Skip to main content

libsignal_service/
account_manager.rs

1use base64::prelude::*;
2use libsignal_core::{DeviceId, E164};
3use rand::{CryptoRng, Rng};
4use reqwest::Method;
5use std::collections::HashMap;
6use std::convert::{TryFrom, TryInto};
7
8use aes::cipher::{KeyIvInit, StreamCipher as _};
9use hmac::{digest::Output, KeyInit};
10use hmac::{Hmac, Mac};
11use libsignal_protocol::{
12    kem, Aci, GenericSignedPreKey, IdentityKey, IdentityKeyPair,
13    IdentityKeyStore, KeyPair, KyberPreKeyRecord, PrivateKey, ProtocolStore,
14    PublicKey, SenderKeyStore, ServiceIdKind, SignedPreKeyRecord, Timestamp,
15};
16use prost::Message;
17use serde::{Deserialize, Serialize};
18use sha2::{Digest, Sha256};
19use tracing_futures::Instrument;
20use zkgroup::profiles::ProfileKey;
21
22use crate::content::ContentBody;
23use crate::pre_keys::{
24    KyberPreKeyEntity, PreKeyEntity, PreKeysStore, SignedPreKeyEntity,
25    PRE_KEY_BATCH_SIZE, PRE_KEY_MINIMUM,
26};
27use crate::prelude::{MessageSender, MessageSenderError};
28use crate::proto::sync_message::PniChangeNumber;
29use crate::proto::{DeviceName, SyncMessage};
30use crate::provisioning::{generate_registration_id, ProvisioningSecrets};
31use crate::push_service::response::{device_limit_reached, error_mapper};
32use crate::push_service::{
33    AvatarWrite, HttpAuthOverride, SignalServiceResponse, DEFAULT_DEVICE_ID,
34};
35use crate::sender::OutgoingPushMessage;
36use crate::service_address::ServiceIdExt;
37use crate::session_store::SessionStoreExt;
38use crate::timestamp::TimestampExt as _;
39use crate::utils::{random_length_padding, BASE64_RELAXED};
40use crate::websocket::account::DeviceInfo;
41use crate::websocket::keys::PreKeyStatus;
42use crate::websocket::registration::CaptchaAttributes;
43use crate::websocket::{self, SignalWebSocket};
44use crate::{
45    configuration::Endpoint,
46    pre_keys::PreKeyState,
47    profile_cipher::{ProfileCipher, ProfileCipherError},
48    profile_name::ProfileName,
49    proto::{ProvisionEnvelope, ProvisionMessage, ProvisioningVersion},
50    provisioning::{ProvisioningCipher, ProvisioningError},
51    push_service::{PushService, ServiceError},
52    utils::serde_base64,
53    websocket::account::AccountAttributes,
54};
55
56// Signal-Server: controllers/DeviceController.java:193
57// (GET /v1/devices/provisioning/code)
58error_mapper! {
59    get_provisioning_code_errors:
60        // 411: length required, DeviceController.java:202
61        LENGTH_REQUIRED => fn device_limit_reached,
62}
63
64// Signal-Server: controllers/ChallengeController.java:91
65// (PUT /v1/challenge)
66error_mapper! {
67    submit_challenge_errors:
68        // 428: precondition required, ChallengeController.java:124
69        PRECONDITION_REQUIRED => ChallengeNotAccepted,
70}
71
72type Aes256Ctr128BE = ctr::Ctr128BE<aes::Aes256>;
73
74pub struct AccountManager {
75    service: PushService,
76    websocket: SignalWebSocket<websocket::Identified>,
77    profile_key: Option<ProfileKey>,
78}
79
80#[derive(thiserror::Error, Debug)]
81pub enum ProfileManagerError {
82    #[error(transparent)]
83    ServiceError(#[from] ServiceError),
84    #[error(transparent)]
85    ProfileCipherError(#[from] ProfileCipherError),
86}
87
88#[derive(Debug, Default, Serialize, Deserialize, Clone)]
89pub struct Profile {
90    pub name: Option<ProfileName<String>>,
91    pub about: Option<String>,
92    pub about_emoji: Option<String>,
93    pub avatar: Option<String>,
94    pub unrestricted_unidentified_access: bool,
95}
96
97impl AccountManager {
98    pub fn new(
99        service: PushService,
100        websocket: SignalWebSocket<websocket::Identified>,
101        profile_key: Option<ProfileKey>,
102    ) -> Self {
103        Self {
104            service,
105            websocket,
106            profile_key,
107        }
108    }
109
110    #[allow(clippy::too_many_arguments)]
111    #[tracing::instrument(skip(self, protocol_store))]
112    pub async fn check_pre_keys<P: PreKeysStore>(
113        &mut self,
114        protocol_store: &mut P,
115        service_id_kind: ServiceIdKind,
116    ) -> Result<bool, ServiceError> {
117        let Some(signed_prekey_id) = protocol_store.signed_prekey_id().await?
118        else {
119            tracing::warn!("No signed prekey found");
120            return Ok(false);
121        };
122        // XXX: should we instead use the `load_last_resort_kyber_pre_keys` method? Or refactor
123        //      those whole traits?
124        let Some(kyber_prekey_id) =
125            protocol_store.last_resort_kyber_prekey_id().await?
126        else {
127            tracing::warn!("No last resort kyber prekey found");
128            return Ok(false);
129        };
130
131        let signed_prekey =
132            protocol_store.get_signed_pre_key(signed_prekey_id).await?;
133        let kyber_prekey =
134            protocol_store.get_kyber_pre_key(kyber_prekey_id).await?;
135
136        // `SHA256(identityKeyBytes || signedEcPreKeyId || signedEcPreKeyIdBytes || lastResortKeyId || lastResortKeyBytes)`
137        let mut hash = Sha256::default();
138        hash.update(
139            protocol_store
140                .get_identity_key_pair()
141                .await?
142                .public_key()
143                .serialize(),
144        );
145        hash.update((u32::from(signed_prekey_id) as u64).to_be_bytes());
146        hash.update(signed_prekey.public_key()?.serialize());
147        hash.update((u32::from(kyber_prekey_id) as u64).to_be_bytes());
148        hash.update(kyber_prekey.public_key()?.serialize());
149
150        self.websocket
151            .check_pre_keys(service_id_kind, hash.finalize().as_ref())
152            .await
153    }
154
155    /// Checks the availability of pre-keys, and updates them as necessary.
156    ///
157    /// Parameters are the protocol's `StoreContext`, and the offsets for the next pre-key and
158    /// signed pre-keys.
159    ///
160    /// Equivalent to Java's RefreshPreKeysJob
161    #[allow(clippy::too_many_arguments)]
162    #[tracing::instrument(skip(self, protocol_store))]
163    pub async fn update_pre_key_bundle<P: PreKeysStore>(
164        &mut self,
165        protocol_store: &mut P,
166        service_id_kind: ServiceIdKind,
167        use_last_resort_key: bool,
168    ) -> Result<(), ServiceError> {
169        let prekey_status = match self
170            .websocket
171            .get_pre_key_status(service_id_kind)
172            .instrument(tracing::span!(
173                tracing::Level::DEBUG,
174                "Fetching pre key status"
175            ))
176            .await
177        {
178            Ok(status) => status,
179            Err(ServiceError::Unauthorized) => {
180                tracing::info!("Got Unauthorized when fetching pre-key status. Assuming first installment.");
181                // Additionally, the second PUT request will fail if this really comes down to an
182                // authorization failure.
183                PreKeyStatus {
184                    count: 0,
185                    pq_count: 0,
186                }
187            },
188            Err(e) => return Err(e),
189        };
190        tracing::trace!("Remaining pre-keys on server: {:?}", prekey_status);
191
192        let check_pre_keys = self
193            .check_pre_keys(protocol_store, service_id_kind)
194            .instrument(tracing::span!(
195                tracing::Level::DEBUG,
196                "Checking pre keys"
197            ))
198            .await?;
199        if !check_pre_keys {
200            tracing::info!(
201                "Last resort pre-keys are not up to date; refreshing."
202            );
203        } else {
204            tracing::debug!("Last resort pre-keys are up to date.");
205        }
206
207        // XXX We should honestly compare the pre-key count with the number of pre-keys we have
208        // locally. If we have more than the server, we should upload them.
209        // Currently the trait doesn't allow us to do that, so we just upload the batch size and
210        // pray.
211        if check_pre_keys
212            && (prekey_status.count >= PRE_KEY_MINIMUM
213                && prekey_status.pq_count >= PRE_KEY_MINIMUM)
214        {
215            if protocol_store.signed_pre_keys_count().await? > 0
216                && protocol_store.kyber_pre_keys_count(true).await? > 0
217                && protocol_store.signed_prekey_id().await?.is_some()
218                && protocol_store
219                    .last_resort_kyber_prekey_id()
220                    .await?
221                    .is_some()
222            {
223                tracing::debug!("Available keys sufficient");
224                return Ok(());
225            }
226            tracing::info!("Available keys sufficient; forcing refresh.");
227        }
228
229        let identity_key_pair = protocol_store
230            .get_identity_key_pair()
231            .instrument(tracing::trace_span!("get identity key pair"))
232            .await?;
233
234        let last_resort_keys = protocol_store
235            .load_last_resort_kyber_pre_keys()
236            .instrument(tracing::trace_span!("fetch last resort key"))
237            .await?;
238
239        // XXX: Maybe this check should be done in the generate_pre_keys function?
240        let has_last_resort_key = !last_resort_keys.is_empty();
241
242        let (pre_keys, signed_pre_key, pq_pre_keys, pq_last_resort_key) =
243            crate::pre_keys::replenish_pre_keys(
244                protocol_store,
245                &mut rand::rng(),
246                &identity_key_pair,
247                use_last_resort_key && !has_last_resort_key,
248                PRE_KEY_BATCH_SIZE,
249                PRE_KEY_BATCH_SIZE,
250            )
251            .await?;
252
253        let pq_last_resort_key = if has_last_resort_key {
254            if last_resort_keys.len() > 1 {
255                tracing::warn!(
256                    "More than one last resort key found; only uploading first"
257                );
258            }
259            Some(KyberPreKeyEntity::try_from(last_resort_keys[0].clone())?)
260        } else {
261            pq_last_resort_key
262                .map(KyberPreKeyEntity::try_from)
263                .transpose()?
264        };
265
266        let identity_key = *identity_key_pair.identity_key();
267
268        let pre_keys: Vec<_> = pre_keys
269            .into_iter()
270            .map(PreKeyEntity::try_from)
271            .collect::<Result<_, _>>()?;
272        let signed_pre_key = signed_pre_key.try_into()?;
273        let pq_pre_keys: Vec<_> = pq_pre_keys
274            .into_iter()
275            .map(KyberPreKeyEntity::try_from)
276            .collect::<Result<_, _>>()?;
277
278        tracing::info!(
279            "Uploading pre-keys: {} one-time, {} PQ, {} PQ last resort",
280            pre_keys.len(),
281            pq_pre_keys.len(),
282            if pq_last_resort_key.is_some() { 1 } else { 0 }
283        );
284
285        let pre_key_state = PreKeyState {
286            pre_keys,
287            signed_pre_key,
288            identity_key,
289            pq_pre_keys,
290            pq_last_resort_key,
291        };
292
293        self.websocket
294            .register_pre_keys(service_id_kind, pre_key_state)
295            .instrument(tracing::span!(
296                tracing::Level::DEBUG,
297                "Uploading pre keys"
298            ))
299            .await?;
300
301        Ok(())
302    }
303
304    async fn new_device_provisioning_code(
305        &mut self,
306    ) -> Result<String, ServiceError> {
307        #[derive(serde::Deserialize)]
308        #[serde(rename_all = "camelCase")]
309        struct DeviceCode {
310            verification_code: String,
311        }
312
313        let dc: DeviceCode = self
314            .service
315            .request(
316                Method::GET,
317                Endpoint::service("/v1/devices/provisioning/code"),
318                HttpAuthOverride::NoOverride,
319            )?
320            .send()
321            .await?
322            .service_error_for_status_with(get_provisioning_code_errors)
323            .await?
324            .json()
325            .await?;
326
327        Ok(dc.verification_code)
328    }
329
330    async fn send_provisioning_message(
331        &mut self,
332        destination: &str,
333        env: ProvisionEnvelope,
334    ) -> Result<(), ServiceError> {
335        #[derive(serde::Serialize)]
336        struct ProvisioningMessage {
337            body: String,
338        }
339
340        let body = env.encode_to_vec();
341
342        self.service
343            .request(
344                Method::PUT,
345                Endpoint::service(format!("/v1/provisioning/{destination}")),
346                HttpAuthOverride::NoOverride,
347            )?
348            .json(&ProvisioningMessage {
349                body: BASE64_RELAXED.encode(body),
350            })
351            .send()
352            .await?
353            .service_error_for_status()
354            .await?;
355
356        Ok(())
357    }
358
359    /// Link a new device, given a tsurl.
360    ///
361    /// Equivalent of Java's `AccountManager::addDevice()`
362    ///
363    /// When calling this, make sure that UnidentifiedDelivery is disabled, ie., that your
364    /// application does not send any unidentified messages before linking is complete.
365    /// Cfr.:
366    /// - `app/src/main/java/org/thoughtcrime/securesms/migrations/LegacyMigrationJob.java`:250 and;
367    /// - `app/src/main/java/org/thoughtcrime/securesms/DeviceActivity.java`:195
368    ///
369    /// ```java
370    /// TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
371    /// ```
372    pub async fn link_device<R: Rng + CryptoRng>(
373        &mut self,
374        csprng: &mut R,
375        url: url::Url,
376        aci_identity_store: &dyn IdentityKeyStore,
377        pni_identity_store: &dyn IdentityKeyStore,
378        secrets: ProvisioningSecrets,
379    ) -> Result<(), ProvisioningError> {
380        let ProvisioningSecrets {
381            credentials,
382            ephemeral_backup_key,
383            account_entropy_pool,
384            media_root_backup_key,
385        } = secrets;
386
387        let query: HashMap<_, _> = url.query_pairs().collect();
388        let ephemeral_id =
389            query.get("uuid").ok_or(ProvisioningError::MissingUuid)?;
390        let pub_key = query
391            .get("pub_key")
392            .ok_or(ProvisioningError::MissingPublicKey)?;
393
394        let pub_key = BASE64_RELAXED
395            .decode(&**pub_key)
396            .map_err(|e| ProvisioningError::InvalidPublicKey(e.into()))?;
397        let pub_key = PublicKey::deserialize(&pub_key)
398            .map_err(|e| ProvisioningError::InvalidPublicKey(e.into()))?;
399
400        let aci_identity_key_pair =
401            aci_identity_store.get_identity_key_pair().await?;
402        let pni_identity_key_pair =
403            pni_identity_store.get_identity_key_pair().await?;
404
405        if credentials.aci.is_none() {
406            tracing::warn!("No local ACI set");
407        }
408        if credentials.pni.is_none() {
409            tracing::warn!("No local PNI set");
410        }
411
412        let provisioning_code = self.new_device_provisioning_code().await?;
413
414        // Same order as in Provisioning.proto for helping comparison.
415        let msg = ProvisionMessage {
416            aci_identity_key_public: Some(
417                aci_identity_key_pair.public_key().serialize().into_vec(),
418            ),
419            aci_identity_key_private: Some(
420                aci_identity_key_pair.private_key().serialize(),
421            ),
422            pni_identity_key_public: Some(
423                pni_identity_key_pair.public_key().serialize().into_vec(),
424            ),
425            pni_identity_key_private: Some(
426                pni_identity_key_pair.private_key().serialize(),
427            ),
428            aci: credentials.aci.as_ref().map(|u| u.to_string()),
429            pni: credentials.pni.as_ref().map(uuid::Uuid::to_string),
430            number: Some(credentials.e164()),
431            provisioning_code: Some(provisioning_code),
432            user_agent: None,
433            profile_key: self.profile_key.as_ref().map(|x| x.bytes.to_vec()),
434            // CURRENT is not exposed by prost :(
435            read_receipts: None,
436            provisioning_version: Some(i32::from(
437                ProvisioningVersion::TabletSupport,
438            ) as _),
439            ephemeral_backup_key: ephemeral_backup_key.map(|k| k.to_vec()), // 32-bytes
440            account_entropy_pool: Some(account_entropy_pool.to_string()),
441            media_root_backup_key: media_root_backup_key.map(|k| k.to_vec()), // 32-bytes
442            aci_binary: credentials.aci.map(|u| u.into_bytes().into()),
443            pni_binary: credentials.pni.map(|u| u.into_bytes().into()),
444        };
445
446        let cipher = ProvisioningCipher::from_public(pub_key);
447
448        let encrypted = cipher.encrypt(csprng, msg)?;
449        self.send_provisioning_message(ephemeral_id, encrypted)
450            .await?;
451        Ok(())
452    }
453
454    pub async fn linked_devices(
455        &mut self,
456        aci_identity_store: &dyn IdentityKeyStore,
457    ) -> Result<Vec<DeviceInfo>, ServiceError> {
458        let device_infos = self.websocket.devices().await?;
459        let aci_identity_keypair =
460            aci_identity_store.get_identity_key_pair().await?;
461
462        device_infos
463            .into_iter()
464            .map(|i| {
465                Ok(DeviceInfo {
466                    id: i.id,
467                    name: i.name.and_then(|s| {
468                        match decrypt_device_name_from_device_info(
469                            &s,
470                            &aci_identity_keypair,
471                        ) {
472                            Ok(name) => Some(name),
473                            Err(e) => {
474                                tracing::error!("{e}");
475                                None
476                            },
477                        }
478                    }),
479                    registration_id: i.registration_id,
480                    last_seen: i.last_seen,
481                    created_at: decrypt_device_created_at_from_device_info(
482                        i.id,
483                        i.registration_id,
484                        &i.created_at_ciphertext,
485                        &aci_identity_keypair,
486                    )?,
487                })
488            })
489            .collect()
490    }
491
492    /// Upload a profile
493    ///
494    /// Panics if no `profile_key` was set.
495    ///
496    /// Convenience method for
497    /// ```ignore
498    /// manager.upload_versioned_profile::<std::io::Cursor<Vec<u8>>, _>(uuid, name, about, about_emoji, _)
499    /// ```
500    /// in which the `retain_avatar` parameter sets whether to remove (`false`) or retain (`true`) the
501    /// currently set avatar.
502    pub async fn upload_versioned_profile_without_avatar<
503        R: Rng + CryptoRng,
504        S: AsRef<str>,
505    >(
506        &mut self,
507        aci: libsignal_protocol::Aci,
508        name: ProfileName<S>,
509        about: Option<String>,
510        about_emoji: Option<String>,
511        retain_avatar: bool,
512        csprng: &mut R,
513    ) -> Result<(), ProfileManagerError> {
514        self.upload_versioned_profile::<std::io::Cursor<Vec<u8>>, _, _>(
515            aci,
516            name,
517            about,
518            about_emoji,
519            if retain_avatar {
520                AvatarWrite::RetainAvatar
521            } else {
522                AvatarWrite::NoAvatar
523            },
524            csprng,
525        )
526        .await?;
527        Ok(())
528    }
529
530    pub async fn retrieve_profile(
531        &mut self,
532        address: Aci,
533    ) -> Result<Profile, ProfileManagerError> {
534        let profile_key =
535            self.profile_key.expect("set profile key in AccountManager");
536
537        let encrypted_profile = self
538            .websocket
539            .retrieve_profile_by_id(address, Some(profile_key))
540            .await?;
541
542        let profile_cipher = ProfileCipher::new(profile_key);
543        Ok(profile_cipher.decrypt(encrypted_profile)?)
544    }
545
546    /// Upload a profile
547    ///
548    /// Panics if no `profile_key` was set.
549    ///
550    /// Returns the avatar url path.
551    pub async fn upload_versioned_profile<
552        's,
553        C: std::io::Read + Send + 's,
554        R: Rng + CryptoRng,
555        S: AsRef<str>,
556    >(
557        &mut self,
558        aci: libsignal_protocol::Aci,
559        name: ProfileName<S>,
560        about: Option<String>,
561        about_emoji: Option<String>,
562        avatar: AvatarWrite<&'s mut C>,
563        csprng: &mut R,
564    ) -> Result<Option<String>, ProfileManagerError> {
565        let profile_key =
566            self.profile_key.expect("set profile key in AccountManager");
567        let profile_cipher = ProfileCipher::new(profile_key);
568
569        // Profile encryption
570        let name = profile_cipher.encrypt_name(name.as_ref(), csprng)?;
571        let about = about.unwrap_or_default();
572        let about = profile_cipher.encrypt_about(about, csprng)?;
573        let about_emoji = about_emoji.unwrap_or_default();
574        let about_emoji = profile_cipher.encrypt_emoji(about_emoji, csprng)?;
575
576        // If avatar -> upload
577        if matches!(avatar, AvatarWrite::NewAvatar(_)) {
578            // FIXME ProfileCipherOutputStream.java
579            // It's just AES GCM, but a bit of work to decently implement it with a stream.
580            unimplemented!("Setting avatar requires ProfileCipherStream")
581        }
582
583        let profile_key = profile_cipher.into_inner();
584        let commitment = profile_key.get_commitment(aci);
585        let profile_key_version = profile_key.get_profile_key_version(aci);
586
587        Ok(self
588            .websocket
589            .write_profile::<C, S>(
590                &profile_key_version,
591                &name,
592                &about,
593                &about_emoji,
594                &commitment,
595                avatar,
596            )
597            .await?)
598    }
599
600    /// Set profile attributes
601    ///
602    /// Signal Android does not allow unsetting voice/video.
603    pub async fn set_account_attributes(
604        &mut self,
605        attributes: AccountAttributes,
606    ) -> Result<(), ServiceError> {
607        self.websocket.set_account_attributes(attributes).await
608    }
609
610    /// Update (encrypted) device name
611    pub async fn update_device_name<R: Rng + CryptoRng>(
612        &mut self,
613        device_id: libsignal_core::DeviceId,
614        device_name: &str,
615        aci: Aci,
616        aci_identity_store: &dyn IdentityKeyStore,
617        csprng: &mut R,
618    ) -> Result<(), ServiceError> {
619        let addr = aci.to_protocol_address(device_id).unwrap();
620        let public_key = aci_identity_store.get_identity(&addr).await?;
621        let Some(public_key) = public_key else {
622            return Err(ServiceError::SendError {
623                reason: format!("public key for device {addr:?} not found"),
624            });
625        };
626        let encrypted_device_name =
627            encrypt_device_name(csprng, device_name, &public_key)?;
628
629        #[derive(Serialize)]
630        #[serde(rename_all = "camelCase")]
631        struct Data {
632            #[serde(with = "serde_base64")]
633            device_name: Vec<u8>,
634        }
635
636        self.service
637            .request(
638                Method::PUT,
639                Endpoint::service(format!(
640                    "/v1/accounts/name?deviceId={}",
641                    device_id
642                )),
643                HttpAuthOverride::NoOverride,
644            )?
645            .json(&Data {
646                device_name: encrypted_device_name.encode_to_vec(),
647            })
648            .send()
649            .await?
650            .service_error_for_status()
651            .await?;
652
653        Ok(())
654    }
655
656    /// Upload a proof-required reCaptcha token and response.
657    ///
658    /// Token gotten originally with HTTP status 428 response to sending a message.
659    /// Captcha gotten from user completing the challenge captcha.
660    ///
661    /// It's either a silent OK, or throws a ServiceError.
662    pub async fn submit_recaptcha_challenge(
663        &mut self,
664        token: &str,
665        captcha: &str,
666    ) -> Result<(), ServiceError> {
667        self.service
668            .request(
669                Method::PUT,
670                Endpoint::service("/v1/challenge"),
671                HttpAuthOverride::NoOverride,
672            )?
673            .json(&CaptchaAttributes {
674                challenge_type: "captcha",
675                token,
676                captcha,
677            })
678            .send()
679            .await?
680            .service_error_for_status_with(submit_challenge_errors)
681            .await?;
682
683        Ok(())
684    }
685
686    /// Initialize PNI on linked devices.
687    ///
688    /// Should be called as the primary device to migrate from pre-PNI to PNI.
689    ///
690    /// This is the equivalent of Android's PnpInitializeDevicesJob or iOS' PniHelloWorldManager.
691    #[tracing::instrument(skip(self, aci_protocol_store, pni_protocol_store, sender, local_aci, csprng), fields(local_aci = local_aci.service_id_string()))]
692    pub async fn pnp_initialize_devices<
693        R: Rng + CryptoRng,
694        AciStore: PreKeysStore + SessionStoreExt,
695        PniStore: PreKeysStore,
696        AciOrPni: ProtocolStore + SenderKeyStore + SessionStoreExt + Sync + Clone,
697    >(
698        &mut self,
699        aci_protocol_store: &mut AciStore,
700        pni_protocol_store: &mut PniStore,
701        mut sender: MessageSender<AciOrPni>,
702        local_aci: Aci,
703        e164: E164,
704        csprng: &mut R,
705    ) -> Result<(), MessageSenderError> {
706        let pni_identity_key_pair =
707            pni_protocol_store.get_identity_key_pair().await?;
708
709        let pni_identity_key = pni_identity_key_pair.identity_key();
710
711        // For every linked device, we generate a new set of pre-keys, and send them to the device.
712        let local_device_ids = aci_protocol_store
713            .get_sub_device_sessions(&local_aci.into())
714            .await?;
715
716        let mut device_messages =
717            Vec::<OutgoingPushMessage>::with_capacity(local_device_ids.len());
718        let mut device_pni_signed_prekeys =
719            HashMap::<String, SignedPreKeyEntity>::with_capacity(
720                local_device_ids.len(),
721            );
722        let mut device_pni_last_resort_kyber_prekeys =
723            HashMap::<String, KyberPreKeyEntity>::with_capacity(
724                local_device_ids.len(),
725            );
726        let mut pni_registration_ids =
727            HashMap::<String, u32>::with_capacity(local_device_ids.len());
728
729        let signature_valid_on_each_signed_pre_key = true;
730        for local_device_id in
731            std::iter::once(*DEFAULT_DEVICE_ID).chain(local_device_ids)
732        {
733            let local_protocol_address =
734                local_aci.to_protocol_address(local_device_id)?;
735            let span = tracing::trace_span!(
736                "filtering devices",
737                address = %local_protocol_address
738            );
739            // Skip if we don't have a session with the device
740            if (local_device_id != *DEFAULT_DEVICE_ID)
741                && aci_protocol_store
742                    .load_session(&local_protocol_address)
743                    .instrument(span)
744                    .await?
745                    .is_none()
746            {
747                tracing::warn!(
748                    "No session with device {}, skipping PNI provisioning",
749                    local_device_id
750                );
751                continue;
752            }
753            let (
754                _pre_keys,
755                signed_pre_key,
756                _kyber_pre_keys,
757                last_resort_kyber_prekey,
758            ) = if local_device_id == *DEFAULT_DEVICE_ID {
759                crate::pre_keys::replenish_pre_keys(
760                    pni_protocol_store,
761                    csprng,
762                    &pni_identity_key_pair,
763                    true,
764                    0,
765                    0,
766                )
767                .await?
768            } else {
769                // Generate a signed prekey
770                let signed_pre_key_pair = KeyPair::generate(csprng);
771                let signed_pre_key_public = signed_pre_key_pair.public_key;
772                let signed_pre_key_signature = pni_identity_key_pair
773                    .private_key()
774                    .calculate_signature(
775                        &signed_pre_key_public.serialize(),
776                        csprng,
777                    )
778                    .map_err(MessageSenderError::InvalidPrivateKey)?;
779
780                let signed_prekey_record = SignedPreKeyRecord::new(
781                    csprng.random_range::<u32, _>(0..0xFFFFFF).into(),
782                    Timestamp::now(),
783                    &signed_pre_key_pair,
784                    &signed_pre_key_signature,
785                );
786
787                // Generate a last-resort Kyber prekey
788                let kyber_pre_key_record = KyberPreKeyRecord::generate(
789                    kem::KeyType::Kyber1024,
790                    csprng.random_range::<u32, _>(0..0xFFFFFF).into(),
791                    pni_identity_key_pair.private_key(),
792                )?;
793                (
794                    vec![],
795                    signed_prekey_record,
796                    vec![],
797                    Some(kyber_pre_key_record),
798                )
799            };
800
801            let registration_id = if local_device_id == *DEFAULT_DEVICE_ID {
802                pni_protocol_store.get_local_registration_id().await?
803            } else {
804                loop {
805                    let regid = generate_registration_id(csprng);
806                    if !pni_registration_ids.iter().any(|(_k, v)| *v == regid) {
807                        break regid;
808                    }
809                }
810            };
811
812            let local_device_id_s = local_device_id.to_string();
813            device_pni_signed_prekeys.insert(
814                local_device_id_s.clone(),
815                SignedPreKeyEntity::try_from(&signed_pre_key)?,
816            );
817            device_pni_last_resort_kyber_prekeys.insert(
818                local_device_id_s.clone(),
819                KyberPreKeyEntity::try_from(
820                    last_resort_kyber_prekey
821                        .as_ref()
822                        .expect("requested last resort key"),
823                )?,
824            );
825            pni_registration_ids
826                .insert(local_device_id_s.clone(), registration_id);
827
828            assert!(_pre_keys.is_empty());
829            assert!(_kyber_pre_keys.is_empty());
830
831            if local_device_id == *DEFAULT_DEVICE_ID {
832                // This is the primary device
833                // We don't need to send a message to the primary device
834                continue;
835            }
836            // cfr. SignalServiceMessageSender::getEncryptedSyncPniInitializeDeviceMessage
837            let msg = SyncMessage {
838                content: Some(
839                    crate::proto::sync_message::Content::PniChangeNumber(
840                        PniChangeNumber {
841                            identity_key_pair: Some(
842                                pni_identity_key_pair.serialize().to_vec(),
843                            ),
844                            signed_pre_key: Some(signed_pre_key.serialize()?),
845                            last_resort_kyber_pre_key: Some(
846                                last_resort_kyber_prekey
847                                    .expect("requested last resort key")
848                                    .serialize()?,
849                            ),
850                            registration_id: Some(registration_id),
851                            new_e164: Some(e164.to_string()),
852                        },
853                    ),
854                ),
855                padding: Some(random_length_padding(csprng, 512)),
856                ..SyncMessage::default()
857            };
858            let content: ContentBody = msg.into();
859            let msg = sender
860                .create_encrypted_message(
861                    &local_aci.into(),
862                    None,
863                    local_device_id,
864                    &content.into_proto().encode_to_vec(),
865                )
866                .await?;
867            device_messages.push(msg);
868        }
869
870        self.websocket
871            .distribute_pni_keys(
872                pni_identity_key,
873                device_messages,
874                device_pni_signed_prekeys,
875                device_pni_last_resort_kyber_prekeys,
876                pni_registration_ids,
877                signature_valid_on_each_signed_pre_key,
878            )
879            .await?;
880
881        Ok(())
882    }
883}
884
885fn calculate_hmac256(
886    mac_key: &[u8],
887    ciphertext: &[u8],
888) -> Result<Output<Hmac<Sha256>>, ServiceError> {
889    let mut mac = Hmac::<Sha256>::new_from_slice(mac_key)
890        .map_err(|_| ServiceError::MacError)?;
891    mac.update(ciphertext);
892    Ok(mac.finalize().into_bytes())
893}
894
895pub fn encrypt_device_name<R: rand::Rng + rand::CryptoRng>(
896    csprng: &mut R,
897    device_name: &str,
898    identity_public: &IdentityKey,
899) -> Result<DeviceName, ServiceError> {
900    let plaintext = device_name.as_bytes().to_vec();
901    let ephemeral_key_pair = KeyPair::generate(csprng);
902
903    let master_secret = ephemeral_key_pair
904        .private_key
905        .calculate_agreement(identity_public.public_key())?;
906
907    let key1 = calculate_hmac256(&master_secret, b"auth")?;
908    let synthetic_iv = calculate_hmac256(&key1, &plaintext)?;
909    let synthetic_iv = &synthetic_iv[..16];
910
911    let key2 = calculate_hmac256(&master_secret, b"cipher")?;
912    let cipher_key = calculate_hmac256(&key2, synthetic_iv)?;
913
914    let mut ciphertext = plaintext;
915
916    const IV: [u8; 16] = [0; 16];
917    let mut cipher = Aes256Ctr128BE::new(&cipher_key, &IV.into());
918    cipher.apply_keystream(&mut ciphertext);
919
920    let device_name = DeviceName {
921        ephemeral_public: Some(
922            ephemeral_key_pair.public_key.serialize().to_vec(),
923        ),
924        synthetic_iv: Some(synthetic_iv.to_vec()),
925        ciphertext: Some(ciphertext),
926    };
927
928    Ok(device_name)
929}
930
931fn decrypt_device_name_from_device_info(
932    string: &str,
933    aci: &IdentityKeyPair,
934) -> Result<String, ServiceError> {
935    let Ok(data) = BASE64_RELAXED.decode(string) else {
936        tracing::trace!(
937            "Device name not base64 encoded, assuming plaintext: {}",
938            string.to_string()
939        );
940        return Ok(string.to_string());
941    };
942    let name = match DeviceName::decode(data.as_slice()) {
943        Ok(decoded) => decoded,
944        Err(_) => {
945            tracing::trace!(
946                "Decoding device name failed, assuming plaintext: {}",
947                string.to_string()
948            );
949            return Ok(string.to_string());
950        },
951    };
952    crate::decrypt_device_name(aci.private_key(), &name)
953}
954
955// Analogous to https://github.com/signalapp/Signal-Android/blob/d88a862e0985cc2bbc463c5f504f5bb4e91ad4fc/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceRepository.kt#L121.
956fn decrypt_device_created_at_from_device_info(
957    id: DeviceId,
958    registration_id: i32,
959    string: &str,
960    aci: &IdentityKeyPair,
961) -> Result<chrono::DateTime<chrono::Utc>, ServiceError> {
962    use signal_crypto::SimpleHpkeReceiver;
963
964    let mut associated_data = [0; 5];
965    associated_data[0] = id.into();
966    associated_data[1..].copy_from_slice(&registration_id.to_be_bytes());
967
968    let data = BASE64_RELAXED.decode(string)?;
969
970    let result =
971        aci.private_key()
972            .open(b"deviceCreatedAt", &associated_data, &data)?;
973
974    let timestamp = i64::from_be_bytes(result.try_into().map_err(|_| {
975        ServiceError::DecryptDeviceInfoFieldError("created-at")
976    })?);
977
978    chrono::DateTime::<chrono::Utc>::from_timestamp_millis(timestamp)
979        .ok_or(ServiceError::DecryptDeviceInfoFieldError("created-at"))
980}
981
982pub fn decrypt_device_name(
983    private_key: &PrivateKey,
984    device_name: &DeviceName,
985) -> Result<String, ServiceError> {
986    let DeviceName {
987        ephemeral_public: Some(ephemeral_public),
988        synthetic_iv: Some(synthetic_iv),
989        ciphertext: Some(ciphertext),
990    } = device_name
991    else {
992        return Err(ServiceError::DecryptDeviceInfoFieldError("name"));
993    };
994
995    let synthetic_iv: [u8; 16] = synthetic_iv[..synthetic_iv.len().min(16)]
996        .try_into()
997        .map_err(|_| ServiceError::MacError)?;
998
999    let ephemeral_public = PublicKey::deserialize(ephemeral_public)?;
1000
1001    let master_secret = private_key.calculate_agreement(&ephemeral_public)?;
1002    let key2 = calculate_hmac256(&master_secret, b"cipher")?;
1003    let cipher_key = calculate_hmac256(&key2, &synthetic_iv)?;
1004
1005    let mut plaintext = ciphertext.to_vec();
1006    const IV: [u8; 16] = [0; 16];
1007    let mut cipher = Aes256Ctr128BE::new(&cipher_key, &IV.into());
1008    cipher.apply_keystream(&mut plaintext);
1009
1010    let key1 = calculate_hmac256(&master_secret, b"auth")?;
1011    let our_synthetic_iv = calculate_hmac256(&key1, &plaintext)?;
1012    let our_synthetic_iv = &our_synthetic_iv[..16];
1013
1014    if synthetic_iv != our_synthetic_iv {
1015        Err(ServiceError::MacError)
1016    } else {
1017        Ok(String::from_utf8_lossy(&plaintext).to_string())
1018    }
1019}
1020
1021#[cfg(test)]
1022mod tests {
1023    use crate::utils::BASE64_RELAXED;
1024    use base64::Engine;
1025    use libsignal_protocol::{IdentityKeyPair, PrivateKey, PublicKey};
1026
1027    use super::DeviceName;
1028
1029    #[test]
1030    fn encrypt_device_name() -> anyhow::Result<()> {
1031        let input_device_name = "Nokia 3310 Millenial Edition";
1032        let mut csprng = rand::rng();
1033        let identity = IdentityKeyPair::generate(&mut csprng);
1034
1035        let device_name = super::encrypt_device_name(
1036            &mut csprng,
1037            input_device_name,
1038            identity.identity_key(),
1039        )?;
1040
1041        let decrypted_device_name =
1042            super::decrypt_device_name(identity.private_key(), &device_name)?;
1043
1044        assert_eq!(input_device_name, decrypted_device_name);
1045
1046        Ok(())
1047    }
1048
1049    #[test]
1050    fn decrypt_device_name() -> anyhow::Result<()> {
1051        let ephemeral_private_key = PrivateKey::deserialize(
1052            &BASE64_RELAXED
1053                .decode("0CgxHjwwblXjvX8sD5wZDWdYToMRf+CZSlgaUrxCGVo=")?,
1054        )?;
1055        let ephemeral_public_key = PublicKey::deserialize(
1056            &BASE64_RELAXED
1057                .decode("BcZS+Lt6yAKbEpXnRX+I5wHqesuvu93Q2V+fjidwW8R6")?,
1058        )?;
1059
1060        let device_name = DeviceName {
1061            ephemeral_public: Some(ephemeral_public_key.serialize().to_vec()),
1062            synthetic_iv: Some(
1063                BASE64_RELAXED.decode("86gekHGmltnnZ9QARhiFcg==")?,
1064            ),
1065            ciphertext: Some(
1066                BASE64_RELAXED
1067                    .decode("MtJ9/9KBWLBVAxfZJD4pLKzP4q+iodRJeCc+/A==")?,
1068            ),
1069        };
1070
1071        let decrypted_device_name =
1072            super::decrypt_device_name(&ephemeral_private_key, &device_name)?;
1073
1074        assert_eq!(decrypted_device_name, "Nokia 3310 Millenial Edition");
1075
1076        Ok(())
1077    }
1078}