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