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::{
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            ephemeral_backup_key,
366            account_entropy_pool,
367            media_root_backup_key,
368        } = secrets;
369
370        let query: HashMap<_, _> = url.query_pairs().collect();
371        let ephemeral_id =
372            query.get("uuid").ok_or(ProvisioningError::MissingUuid)?;
373        let pub_key = query
374            .get("pub_key")
375            .ok_or(ProvisioningError::MissingPublicKey)?;
376
377        let pub_key = BASE64_RELAXED
378            .decode(&**pub_key)
379            .map_err(|e| ProvisioningError::InvalidPublicKey(e.into()))?;
380        let pub_key = PublicKey::deserialize(&pub_key)
381            .map_err(|e| ProvisioningError::InvalidPublicKey(e.into()))?;
382
383        let aci_identity_key_pair =
384            aci_identity_store.get_identity_key_pair().await?;
385        let pni_identity_key_pair =
386            pni_identity_store.get_identity_key_pair().await?;
387
388        if credentials.aci.is_none() {
389            tracing::warn!("No local ACI set");
390        }
391        if credentials.pni.is_none() {
392            tracing::warn!("No local PNI set");
393        }
394
395        let provisioning_code = self.new_device_provisioning_code().await?;
396
397        // Same order as in Provisioning.proto for helping comparison.
398        let msg = ProvisionMessage {
399            aci_identity_key_public: Some(
400                aci_identity_key_pair.public_key().serialize().into_vec(),
401            ),
402            aci_identity_key_private: Some(
403                aci_identity_key_pair.private_key().serialize(),
404            ),
405            pni_identity_key_public: Some(
406                pni_identity_key_pair.public_key().serialize().into_vec(),
407            ),
408            pni_identity_key_private: Some(
409                pni_identity_key_pair.private_key().serialize(),
410            ),
411            aci: credentials.aci.as_ref().map(|u| u.to_string()),
412            pni: credentials.pni.as_ref().map(uuid::Uuid::to_string),
413            number: Some(credentials.e164()),
414            provisioning_code: Some(provisioning_code),
415            user_agent: None,
416            profile_key: self.profile_key.as_ref().map(|x| x.bytes.to_vec()),
417            // CURRENT is not exposed by prost :(
418            read_receipts: None,
419            provisioning_version: Some(i32::from(
420                ProvisioningVersion::TabletSupport,
421            ) as _),
422            ephemeral_backup_key: ephemeral_backup_key.map(|k| k.to_vec()), // 32-bytes
423            account_entropy_pool: Some(account_entropy_pool.to_string()),
424            media_root_backup_key: media_root_backup_key.map(|k| k.to_vec()), // 32-bytes
425            aci_binary: credentials.aci.map(|u| u.into_bytes().into()),
426            pni_binary: credentials.pni.map(|u| u.into_bytes().into()),
427        };
428
429        let cipher = ProvisioningCipher::from_public(pub_key);
430
431        let encrypted = cipher.encrypt(csprng, msg)?;
432        self.send_provisioning_message(ephemeral_id, encrypted)
433            .await?;
434        Ok(())
435    }
436
437    pub async fn linked_devices(
438        &mut self,
439        aci_identity_store: &dyn IdentityKeyStore,
440    ) -> Result<Vec<DeviceInfo>, ServiceError> {
441        let device_infos = self.websocket.devices().await?;
442        let aci_identity_keypair =
443            aci_identity_store.get_identity_key_pair().await?;
444
445        device_infos
446            .into_iter()
447            .map(|i| {
448                Ok(DeviceInfo {
449                    id: i.id,
450                    name: i.name.and_then(|s| {
451                        match decrypt_device_name_from_device_info(
452                            &s,
453                            &aci_identity_keypair,
454                        ) {
455                            Ok(name) => Some(name),
456                            Err(e) => {
457                                tracing::error!("{e}");
458                                None
459                            },
460                        }
461                    }),
462                    registration_id: i.registration_id,
463                    last_seen: i.last_seen,
464                    created_at: decrypt_device_created_at_from_device_info(
465                        i.id,
466                        i.registration_id,
467                        &i.created_at_ciphertext,
468                        &aci_identity_keypair,
469                    )?,
470                })
471            })
472            .collect()
473    }
474
475    /// Upload a profile
476    ///
477    /// Panics if no `profile_key` was set.
478    ///
479    /// Convenience method for
480    /// ```ignore
481    /// manager.upload_versioned_profile::<std::io::Cursor<Vec<u8>>, _>(uuid, name, about, about_emoji, _)
482    /// ```
483    /// in which the `retain_avatar` parameter sets whether to remove (`false`) or retain (`true`) the
484    /// currently set avatar.
485    pub async fn upload_versioned_profile_without_avatar<
486        R: Rng + CryptoRng,
487        S: AsRef<str>,
488    >(
489        &mut self,
490        aci: libsignal_protocol::Aci,
491        name: ProfileName<S>,
492        about: Option<String>,
493        about_emoji: Option<String>,
494        retain_avatar: bool,
495        csprng: &mut R,
496    ) -> Result<(), ProfileManagerError> {
497        self.upload_versioned_profile::<std::io::Cursor<Vec<u8>>, _, _>(
498            aci,
499            name,
500            about,
501            about_emoji,
502            if retain_avatar {
503                AvatarWrite::RetainAvatar
504            } else {
505                AvatarWrite::NoAvatar
506            },
507            csprng,
508        )
509        .await?;
510        Ok(())
511    }
512
513    pub async fn retrieve_profile(
514        &mut self,
515        address: Aci,
516    ) -> Result<Profile, ProfileManagerError> {
517        let profile_key =
518            self.profile_key.expect("set profile key in AccountManager");
519
520        let encrypted_profile = self
521            .websocket
522            .retrieve_profile_by_id(address, Some(profile_key))
523            .await?;
524
525        let profile_cipher = ProfileCipher::new(profile_key);
526        Ok(profile_cipher.decrypt(encrypted_profile)?)
527    }
528
529    /// Upload a profile
530    ///
531    /// Panics if no `profile_key` was set.
532    ///
533    /// Returns the avatar url path.
534    pub async fn upload_versioned_profile<
535        's,
536        C: std::io::Read + Send + 's,
537        R: Rng + CryptoRng,
538        S: AsRef<str>,
539    >(
540        &mut self,
541        aci: libsignal_protocol::Aci,
542        name: ProfileName<S>,
543        about: Option<String>,
544        about_emoji: Option<String>,
545        avatar: AvatarWrite<&'s mut C>,
546        csprng: &mut R,
547    ) -> Result<Option<String>, ProfileManagerError> {
548        let profile_key =
549            self.profile_key.expect("set profile key in AccountManager");
550        let profile_cipher = ProfileCipher::new(profile_key);
551
552        // Profile encryption
553        let name = profile_cipher.encrypt_name(name.as_ref(), csprng)?;
554        let about = about.unwrap_or_default();
555        let about = profile_cipher.encrypt_about(about, csprng)?;
556        let about_emoji = about_emoji.unwrap_or_default();
557        let about_emoji = profile_cipher.encrypt_emoji(about_emoji, csprng)?;
558
559        // If avatar -> upload
560        if matches!(avatar, AvatarWrite::NewAvatar(_)) {
561            // FIXME ProfileCipherOutputStream.java
562            // It's just AES GCM, but a bit of work to decently implement it with a stream.
563            unimplemented!("Setting avatar requires ProfileCipherStream")
564        }
565
566        let profile_key = profile_cipher.into_inner();
567        let commitment = profile_key.get_commitment(aci);
568        let profile_key_version = profile_key.get_profile_key_version(aci);
569
570        Ok(self
571            .websocket
572            .write_profile::<C, S>(
573                &profile_key_version,
574                &name,
575                &about,
576                &about_emoji,
577                &commitment,
578                avatar,
579            )
580            .await?)
581    }
582
583    /// Set profile attributes
584    ///
585    /// Signal Android does not allow unsetting voice/video.
586    pub async fn set_account_attributes(
587        &mut self,
588        attributes: AccountAttributes,
589    ) -> Result<(), ServiceError> {
590        self.websocket.set_account_attributes(attributes).await
591    }
592
593    /// Update (encrypted) device name
594    pub async fn update_device_name<R: Rng + CryptoRng>(
595        &mut self,
596        device_id: libsignal_core::DeviceId,
597        device_name: &str,
598        aci: Aci,
599        aci_identity_store: &dyn IdentityKeyStore,
600        csprng: &mut R,
601    ) -> Result<(), ServiceError> {
602        let addr = aci.to_protocol_address(device_id).unwrap();
603        let public_key = aci_identity_store.get_identity(&addr).await?;
604        let Some(public_key) = public_key else {
605            return Err(ServiceError::SendError {
606                reason: format!("public key for device {addr:?} not found"),
607            });
608        };
609        let encrypted_device_name =
610            encrypt_device_name(csprng, device_name, &public_key)?;
611
612        #[derive(Serialize)]
613        #[serde(rename_all = "camelCase")]
614        struct Data {
615            #[serde(with = "serde_base64")]
616            device_name: Vec<u8>,
617        }
618
619        self.service
620            .request(
621                Method::PUT,
622                Endpoint::service(format!(
623                    "/v1/accounts/name?deviceId={}",
624                    device_id
625                )),
626                HttpAuthOverride::NoOverride,
627            )?
628            .json(&Data {
629                device_name: encrypted_device_name.encode_to_vec(),
630            })
631            .send()
632            .await?
633            .service_error_for_status()
634            .await?;
635
636        Ok(())
637    }
638
639    /// Upload a proof-required reCaptcha token and response.
640    ///
641    /// Token gotten originally with HTTP status 428 response to sending a message.
642    /// Captcha gotten from user completing the challenge captcha.
643    ///
644    /// It's either a silent OK, or throws a ServiceError.
645    pub async fn submit_recaptcha_challenge(
646        &mut self,
647        token: &str,
648        captcha: &str,
649    ) -> Result<(), ServiceError> {
650        self.service
651            .request(
652                Method::PUT,
653                Endpoint::service("/v1/challenge"),
654                HttpAuthOverride::NoOverride,
655            )?
656            .json(&CaptchaAttributes {
657                challenge_type: "captcha",
658                token,
659                captcha,
660            })
661            .send()
662            .await?
663            .service_error_for_status()
664            .await?;
665
666        Ok(())
667    }
668
669    /// Initialize PNI on linked devices.
670    ///
671    /// Should be called as the primary device to migrate from pre-PNI to PNI.
672    ///
673    /// This is the equivalent of Android's PnpInitializeDevicesJob or iOS' PniHelloWorldManager.
674    #[tracing::instrument(skip(self, aci_protocol_store, pni_protocol_store, sender, local_aci, csprng), fields(local_aci = local_aci.service_id_string()))]
675    pub async fn pnp_initialize_devices<
676        R: Rng + CryptoRng,
677        AciStore: PreKeysStore + SessionStoreExt,
678        PniStore: PreKeysStore,
679        AciOrPni: ProtocolStore + SenderKeyStore + SessionStoreExt + Sync + Clone,
680    >(
681        &mut self,
682        aci_protocol_store: &mut AciStore,
683        pni_protocol_store: &mut PniStore,
684        mut sender: MessageSender<AciOrPni>,
685        local_aci: Aci,
686        e164: E164,
687        csprng: &mut R,
688    ) -> Result<(), MessageSenderError> {
689        let pni_identity_key_pair =
690            pni_protocol_store.get_identity_key_pair().await?;
691
692        let pni_identity_key = pni_identity_key_pair.identity_key();
693
694        // For every linked device, we generate a new set of pre-keys, and send them to the device.
695        let local_device_ids = aci_protocol_store
696            .get_sub_device_sessions(&local_aci.into())
697            .await?;
698
699        let mut device_messages =
700            Vec::<OutgoingPushMessage>::with_capacity(local_device_ids.len());
701        let mut device_pni_signed_prekeys =
702            HashMap::<String, SignedPreKeyEntity>::with_capacity(
703                local_device_ids.len(),
704            );
705        let mut device_pni_last_resort_kyber_prekeys =
706            HashMap::<String, KyberPreKeyEntity>::with_capacity(
707                local_device_ids.len(),
708            );
709        let mut pni_registration_ids =
710            HashMap::<String, u32>::with_capacity(local_device_ids.len());
711
712        let signature_valid_on_each_signed_pre_key = true;
713        for local_device_id in
714            std::iter::once(*DEFAULT_DEVICE_ID).chain(local_device_ids)
715        {
716            let local_protocol_address =
717                local_aci.to_protocol_address(local_device_id)?;
718            let span = tracing::trace_span!(
719                "filtering devices",
720                address = %local_protocol_address
721            );
722            // Skip if we don't have a session with the device
723            if (local_device_id != *DEFAULT_DEVICE_ID)
724                && aci_protocol_store
725                    .load_session(&local_protocol_address)
726                    .instrument(span)
727                    .await?
728                    .is_none()
729            {
730                tracing::warn!(
731                    "No session with device {}, skipping PNI provisioning",
732                    local_device_id
733                );
734                continue;
735            }
736            let (
737                _pre_keys,
738                signed_pre_key,
739                _kyber_pre_keys,
740                last_resort_kyber_prekey,
741            ) = if local_device_id == *DEFAULT_DEVICE_ID {
742                crate::pre_keys::replenish_pre_keys(
743                    pni_protocol_store,
744                    csprng,
745                    &pni_identity_key_pair,
746                    true,
747                    0,
748                    0,
749                )
750                .await?
751            } else {
752                // Generate a signed prekey
753                let signed_pre_key_pair = KeyPair::generate(csprng);
754                let signed_pre_key_public = signed_pre_key_pair.public_key;
755                let signed_pre_key_signature = pni_identity_key_pair
756                    .private_key()
757                    .calculate_signature(
758                        &signed_pre_key_public.serialize(),
759                        csprng,
760                    )
761                    .map_err(MessageSenderError::InvalidPrivateKey)?;
762
763                let signed_prekey_record = SignedPreKeyRecord::new(
764                    csprng.random_range::<u32, _>(0..0xFFFFFF).into(),
765                    Timestamp::now(),
766                    &signed_pre_key_pair,
767                    &signed_pre_key_signature,
768                );
769
770                // Generate a last-resort Kyber prekey
771                let kyber_pre_key_record = KyberPreKeyRecord::generate(
772                    kem::KeyType::Kyber1024,
773                    csprng.random_range::<u32, _>(0..0xFFFFFF).into(),
774                    pni_identity_key_pair.private_key(),
775                )?;
776                (
777                    vec![],
778                    signed_prekey_record,
779                    vec![],
780                    Some(kyber_pre_key_record),
781                )
782            };
783
784            let registration_id = if local_device_id == *DEFAULT_DEVICE_ID {
785                pni_protocol_store.get_local_registration_id().await?
786            } else {
787                loop {
788                    let regid = generate_registration_id(csprng);
789                    if !pni_registration_ids.iter().any(|(_k, v)| *v == regid) {
790                        break regid;
791                    }
792                }
793            };
794
795            let local_device_id_s = local_device_id.to_string();
796            device_pni_signed_prekeys.insert(
797                local_device_id_s.clone(),
798                SignedPreKeyEntity::try_from(&signed_pre_key)?,
799            );
800            device_pni_last_resort_kyber_prekeys.insert(
801                local_device_id_s.clone(),
802                KyberPreKeyEntity::try_from(
803                    last_resort_kyber_prekey
804                        .as_ref()
805                        .expect("requested last resort key"),
806                )?,
807            );
808            pni_registration_ids
809                .insert(local_device_id_s.clone(), registration_id);
810
811            assert!(_pre_keys.is_empty());
812            assert!(_kyber_pre_keys.is_empty());
813
814            if local_device_id == *DEFAULT_DEVICE_ID {
815                // This is the primary device
816                // We don't need to send a message to the primary device
817                continue;
818            }
819            // cfr. SignalServiceMessageSender::getEncryptedSyncPniInitializeDeviceMessage
820            let msg = SyncMessage {
821                content: Some(
822                    crate::proto::sync_message::Content::PniChangeNumber(
823                        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                    ),
837                ),
838                padding: Some(random_length_padding(csprng, 512)),
839                ..SyncMessage::default()
840            };
841            let content: ContentBody = msg.into();
842            let msg = sender
843                .create_encrypted_message(
844                    &local_aci.into(),
845                    None,
846                    local_device_id,
847                    &content.into_proto().encode_to_vec(),
848                )
849                .await?;
850            device_messages.push(msg);
851        }
852
853        self.websocket
854            .distribute_pni_keys(
855                pni_identity_key,
856                device_messages,
857                device_pni_signed_prekeys,
858                device_pni_last_resort_kyber_prekeys,
859                pni_registration_ids,
860                signature_valid_on_each_signed_pre_key,
861            )
862            .await?;
863
864        Ok(())
865    }
866}
867
868fn calculate_hmac256(
869    mac_key: &[u8],
870    ciphertext: &[u8],
871) -> Result<Output<Hmac<Sha256>>, ServiceError> {
872    let mut mac = Hmac::<Sha256>::new_from_slice(mac_key)
873        .map_err(|_| ServiceError::MacError)?;
874    mac.update(ciphertext);
875    Ok(mac.finalize().into_bytes())
876}
877
878pub fn encrypt_device_name<R: rand::Rng + rand::CryptoRng>(
879    csprng: &mut R,
880    device_name: &str,
881    identity_public: &IdentityKey,
882) -> Result<DeviceName, ServiceError> {
883    let plaintext = device_name.as_bytes().to_vec();
884    let ephemeral_key_pair = KeyPair::generate(csprng);
885
886    let master_secret = ephemeral_key_pair
887        .private_key
888        .calculate_agreement(identity_public.public_key())?;
889
890    let key1 = calculate_hmac256(&master_secret, b"auth")?;
891    let synthetic_iv = calculate_hmac256(&key1, &plaintext)?;
892    let synthetic_iv = &synthetic_iv[..16];
893
894    let key2 = calculate_hmac256(&master_secret, b"cipher")?;
895    let cipher_key = calculate_hmac256(&key2, synthetic_iv)?;
896
897    let mut ciphertext = plaintext;
898
899    const IV: [u8; 16] = [0; 16];
900    let mut cipher = Aes256Ctr128BE::new(&cipher_key, &IV.into());
901    cipher.apply_keystream(&mut ciphertext);
902
903    let device_name = DeviceName {
904        ephemeral_public: Some(
905            ephemeral_key_pair.public_key.serialize().to_vec(),
906        ),
907        synthetic_iv: Some(synthetic_iv.to_vec()),
908        ciphertext: Some(ciphertext),
909    };
910
911    Ok(device_name)
912}
913
914fn decrypt_device_name_from_device_info(
915    string: &str,
916    aci: &IdentityKeyPair,
917) -> Result<String, ServiceError> {
918    let Ok(data) = BASE64_RELAXED.decode(string) else {
919        tracing::trace!(
920            "Device name not base64 encoded, assuming plaintext: {}",
921            string.to_string()
922        );
923        return Ok(string.to_string());
924    };
925    let name = match DeviceName::decode(data.as_slice()) {
926        Ok(decoded) => decoded,
927        Err(_) => {
928            tracing::trace!(
929                "Decoding device name failed, assuming plaintext: {}",
930                string.to_string()
931            );
932            return Ok(string.to_string());
933        },
934    };
935    crate::decrypt_device_name(aci.private_key(), &name)
936}
937
938// Analogous to https://github.com/signalapp/Signal-Android/blob/d88a862e0985cc2bbc463c5f504f5bb4e91ad4fc/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceRepository.kt#L121.
939fn decrypt_device_created_at_from_device_info(
940    id: DeviceId,
941    registration_id: i32,
942    string: &str,
943    aci: &IdentityKeyPair,
944) -> Result<chrono::DateTime<chrono::Utc>, ServiceError> {
945    use signal_crypto::SimpleHpkeReceiver;
946
947    let mut associated_data = [0; 5];
948    associated_data[0] = id.into();
949    associated_data[1..].copy_from_slice(&registration_id.to_be_bytes());
950
951    let data = BASE64_RELAXED.decode(string)?;
952
953    let result =
954        aci.private_key()
955            .open(b"deviceCreatedAt", &associated_data, &data)?;
956
957    let timestamp = i64::from_be_bytes(result.try_into().map_err(|_| {
958        ServiceError::DecryptDeviceInfoFieldError("created-at")
959    })?);
960
961    chrono::DateTime::<chrono::Utc>::from_timestamp_millis(timestamp)
962        .ok_or(ServiceError::DecryptDeviceInfoFieldError("created-at"))
963}
964
965pub fn decrypt_device_name(
966    private_key: &PrivateKey,
967    device_name: &DeviceName,
968) -> Result<String, ServiceError> {
969    let DeviceName {
970        ephemeral_public: Some(ephemeral_public),
971        synthetic_iv: Some(synthetic_iv),
972        ciphertext: Some(ciphertext),
973    } = device_name
974    else {
975        return Err(ServiceError::DecryptDeviceInfoFieldError("name"));
976    };
977
978    let synthetic_iv: [u8; 16] = synthetic_iv[..synthetic_iv.len().min(16)]
979        .try_into()
980        .map_err(|_| ServiceError::MacError)?;
981
982    let ephemeral_public = PublicKey::deserialize(ephemeral_public)?;
983
984    let master_secret = private_key.calculate_agreement(&ephemeral_public)?;
985    let key2 = calculate_hmac256(&master_secret, b"cipher")?;
986    let cipher_key = calculate_hmac256(&key2, &synthetic_iv)?;
987
988    let mut plaintext = ciphertext.to_vec();
989    const IV: [u8; 16] = [0; 16];
990    let mut cipher = Aes256Ctr128BE::new(&cipher_key, &IV.into());
991    cipher.apply_keystream(&mut plaintext);
992
993    let key1 = calculate_hmac256(&master_secret, b"auth")?;
994    let our_synthetic_iv = calculate_hmac256(&key1, &plaintext)?;
995    let our_synthetic_iv = &our_synthetic_iv[..16];
996
997    if synthetic_iv != our_synthetic_iv {
998        Err(ServiceError::MacError)
999    } else {
1000        Ok(String::from_utf8_lossy(&plaintext).to_string())
1001    }
1002}
1003
1004#[cfg(test)]
1005mod tests {
1006    use crate::utils::BASE64_RELAXED;
1007    use base64::Engine;
1008    use libsignal_protocol::{IdentityKeyPair, PrivateKey, PublicKey};
1009
1010    use super::DeviceName;
1011
1012    #[test]
1013    fn encrypt_device_name() -> anyhow::Result<()> {
1014        let input_device_name = "Nokia 3310 Millenial Edition";
1015        let mut csprng = rand::rng();
1016        let identity = IdentityKeyPair::generate(&mut csprng);
1017
1018        let device_name = super::encrypt_device_name(
1019            &mut csprng,
1020            input_device_name,
1021            identity.identity_key(),
1022        )?;
1023
1024        let decrypted_device_name =
1025            super::decrypt_device_name(identity.private_key(), &device_name)?;
1026
1027        assert_eq!(input_device_name, decrypted_device_name);
1028
1029        Ok(())
1030    }
1031
1032    #[test]
1033    fn decrypt_device_name() -> anyhow::Result<()> {
1034        let ephemeral_private_key = PrivateKey::deserialize(
1035            &BASE64_RELAXED
1036                .decode("0CgxHjwwblXjvX8sD5wZDWdYToMRf+CZSlgaUrxCGVo=")?,
1037        )?;
1038        let ephemeral_public_key = PublicKey::deserialize(
1039            &BASE64_RELAXED
1040                .decode("BcZS+Lt6yAKbEpXnRX+I5wHqesuvu93Q2V+fjidwW8R6")?,
1041        )?;
1042
1043        let device_name = DeviceName {
1044            ephemeral_public: Some(ephemeral_public_key.serialize().to_vec()),
1045            synthetic_iv: Some(
1046                BASE64_RELAXED.decode("86gekHGmltnnZ9QARhiFcg==")?,
1047            ),
1048            ciphertext: Some(
1049                BASE64_RELAXED
1050                    .decode("MtJ9/9KBWLBVAxfZJD4pLKzP4q+iodRJeCc+/A==")?,
1051            ),
1052        };
1053
1054        let decrypted_device_name =
1055            super::decrypt_device_name(&ephemeral_private_key, &device_name)?;
1056
1057        assert_eq!(decrypted_device_name, "Nokia 3310 Millenial Edition");
1058
1059        Ok(())
1060    }
1061}