Skip to main content

libsignal_service/provisioning/
mod.rs

1mod cipher;
2mod pipe;
3
4use std::array::TryFromSliceError;
5use std::convert::TryInto;
6
7use libsignal_account_keys::{AccountEntropyPool, InvalidAccountEntropyPool};
8
9pub use cipher::ProvisioningCipher;
10
11use base64::Engine;
12use futures::StreamExt;
13use futures::{channel::mpsc::Sender, pin_mut, SinkExt};
14use libsignal_core::curve::CurveError;
15use libsignal_core::E164;
16use libsignal_protocol::{
17    DeviceId, IdentityKey, IdentityKeyPair, PrivateKey, PublicKey,
18};
19use prost::Message;
20use serde::Deserialize;
21use url::Url;
22use uuid::Uuid;
23use zkgroup::profiles::ProfileKey;
24
25use pipe::{ProvisioningPipe, ProvisioningStep};
26
27use crate::messagepipe::ServiceCredentials;
28use crate::prelude::ServiceError;
29use crate::push_service::linking::{
30    LinkAccountAttributes, LinkCapabilities, LinkRequest, LinkResponse,
31};
32use crate::utils::BASE64_RELAXED;
33use crate::websocket::registration::DeviceActivationRequest;
34use crate::{
35    account_manager::encrypt_device_name,
36    pre_keys::PreKeysStore,
37    push_service::{HttpAuth, PushService, ServiceIds},
38};
39
40pub use crate::proto::{
41    ProvisionEnvelope, ProvisionMessage, ProvisioningVersion,
42};
43
44pub struct ProvisioningSecrets {
45    pub credentials: ServiceCredentials,
46    pub ephemeral_backup_key: Option<[u8; 32]>,
47    pub account_entropy_pool: AccountEntropyPool,
48    pub media_root_backup_key: Option<[u8; 32]>,
49}
50
51#[derive(thiserror::Error, Debug)]
52pub enum ProvisioningError {
53    #[error("no provisioning URL received")]
54    MissingUrl,
55    #[error("bad version number (unsupported)")]
56    BadVersionNumber,
57    #[error("missing public key")]
58    MissingPublicKey,
59    #[error("missing private key")]
60    MissingPrivateKey,
61    #[error("invalid public key")]
62    InvalidPublicKey(InvalidKeyError),
63    #[error("invalid privat key")]
64    InvalidPrivateKey(InvalidKeyError),
65    #[error("missing UUID")]
66    MissingUuid,
67    #[error("no provisioning message received")]
68    MissingMessage,
69    #[error("missing profile key")]
70    MissingProfileKey,
71    #[error("missing phone number")]
72    MissingPhoneNumber,
73    #[error("invalid phone number: {0}")]
74    InvalidPhoneNumber(<E164 as std::str::FromStr>::Err),
75    #[error("missing provisioning code")]
76    MissingProvisioningCode,
77    #[error("mismatched MAC")]
78    MismatchedMac,
79    #[error("AES CBC padding error: {0}")]
80    AesPaddingError(aes::cipher::block_padding::Error),
81
82    #[error("invalid provisioning step received")]
83    InvalidStep,
84
85    #[error("Protobuf decoding error: {0}")]
86    DecodeError(#[from] prost::DecodeError),
87    #[error("Websocket error: {reason}")]
88    WsError { reason: String },
89    #[error("Websocket closing")]
90    WsClosing,
91    #[error("Service error: {0}")]
92    ServiceError(#[from] ServiceError),
93    #[error("libsignal-protocol error: {0}")]
94    ProtocolError(#[from] libsignal_protocol::SignalProtocolError),
95    #[error("invalid device ID: {0}")]
96    InvalidDeviceId(#[from] libsignal_core::InvalidDeviceId),
97    #[error("ProvisioningCipher in encrypt-only mode")]
98    EncryptOnlyProvisioningCipher,
99    #[error("invalid profile key bytes")]
100    InvalidProfileKey(TryFromSliceError),
101    #[error("invalid account entropy pool: {0}")]
102    InvalidAccountEntropyPool(#[from] InvalidAccountEntropyPool),
103    #[error("invalid ephemeral backup key length: expected 32 bytes, got {0}")]
104    InvalidEphemeralBackupKey(usize),
105}
106
107impl ProvisioningError {
108    pub fn invalid_public_key(e: impl Into<InvalidKeyError>) -> Self {
109        ProvisioningError::InvalidPublicKey(e.into())
110    }
111
112    pub fn invalid_private_key(e: impl Into<InvalidKeyError>) -> Self {
113        ProvisioningError::InvalidPrivateKey(e.into())
114    }
115}
116
117#[derive(Debug, thiserror::Error)]
118pub enum InvalidKeyError {
119    #[error("curve error: {0}")]
120    Curve(#[from] CurveError),
121    #[error("base64 decoding error: {0}")]
122    Base64(#[from] base64::DecodeError),
123    #[error("protocol error: {0}")]
124    Protocol(#[from] libsignal_protocol::SignalProtocolError),
125}
126
127pub fn generate_registration_id<R: rand::Rng + rand::CryptoRng>(
128    csprng: &mut R,
129) -> u32 {
130    csprng.random_range(1..16380)
131}
132
133#[derive(Debug, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct ConfirmCodeResponse {
136    pub uuid: Uuid,
137    pub storage_capable: bool,
138}
139
140#[derive(Debug)]
141#[allow(clippy::large_enum_variant)]
142pub enum SecondaryDeviceProvisioning {
143    Url(Url),
144    NewDeviceRegistration(NewDeviceRegistration),
145}
146
147#[derive(derive_more::Debug)]
148pub struct NewDeviceRegistration {
149    pub phone_number: libsignal_core::E164,
150    pub device_id: DeviceId,
151    pub registration_id: u32,
152    pub pni_registration_id: u32,
153    pub service_ids: ServiceIds,
154    #[debug(ignore)]
155    pub aci_private_key: PrivateKey,
156    pub aci_public_key: IdentityKey,
157    #[debug(ignore)]
158    pub pni_private_key: PrivateKey,
159    pub pni_public_key: IdentityKey,
160    #[debug(ignore)]
161    pub profile_key: ProfileKey,
162    /// Account Entropy Pool — the modern `accountEntropyPool` field 15.
163    /// 64-character alphanumeric string from which the canonical master
164    /// key is derived (`AccountEntropyPool::derive_svr_key`). When present,
165    /// it should be preferred over the deprecated `master_key` field.
166    #[debug(ignore)]
167    pub account_entropy_pool: Option<AccountEntropyPool>,
168    /// One-time key for an initial linked-device message-history transfer.
169    #[debug(ignore)]
170    pub ephemeral_backup_key: Option<[u8; 32]>,
171}
172
173pub async fn link_device<
174    R: rand::Rng + rand::CryptoRng,
175    Aci: PreKeysStore,
176    Pni: PreKeysStore,
177>(
178    aci_store: &mut Aci,
179    pni_store: &mut Pni,
180    csprng: &mut R,
181    mut push_service: PushService,
182    password: &str,
183    device_name: &str,
184    mut tx: Sender<SecondaryDeviceProvisioning>,
185) -> Result<(), ProvisioningError> {
186    // open a websocket without authentication, to receive a tsurl://
187    let ws = push_service
188        .ws(
189            "/v1/websocket/provisioning/",
190            "/v1/keepalive/provisioning",
191            &[],
192            None,
193        )
194        .await?;
195
196    let registration_id = csprng.random_range(1..256);
197    let pni_registration_id = csprng.random_range(1..256);
198
199    let provisioning_pipe = ProvisioningPipe::from_socket(ws, csprng);
200    let provision_stream = provisioning_pipe.stream();
201    pin_mut!(provision_stream);
202
203    if let ProvisioningStep::Url(url) = provision_stream
204        .next()
205        .await
206        .ok_or(ProvisioningError::MissingUrl)??
207    {
208        tx.send(SecondaryDeviceProvisioning::Url(url))
209            .await
210            .expect("failed to send provisioning Url in channel");
211    } else {
212        return Err(ProvisioningError::InvalidStep);
213    }
214
215    if let ProvisioningStep::Message(message) =
216        provision_stream
217            .next()
218            .await
219            .ok_or(ProvisioningError::MissingMessage)??
220    {
221        let aci_public_key = PublicKey::deserialize(
222            &message
223                .aci_identity_key_public
224                .ok_or(ProvisioningError::MissingPublicKey)?,
225        )
226        .map_err(ProvisioningError::invalid_public_key)?;
227        let aci_public_key = IdentityKey::new(aci_public_key);
228
229        let aci_private_key = PrivateKey::deserialize(
230            &message
231                .aci_identity_key_private
232                .ok_or(ProvisioningError::MissingPrivateKey)?,
233        )
234        .map_err(ProvisioningError::invalid_private_key)?;
235
236        let pni_public_key = PublicKey::deserialize(
237            &message
238                .pni_identity_key_public
239                .ok_or(ProvisioningError::MissingPublicKey)?,
240        )
241        .map_err(ProvisioningError::invalid_public_key)?;
242        let pni_public_key = IdentityKey::new(pni_public_key);
243
244        let pni_private_key = PrivateKey::deserialize(
245            &message
246                .pni_identity_key_private
247                .ok_or(ProvisioningError::MissingPrivateKey)?,
248        )
249        .map_err(ProvisioningError::invalid_private_key)?;
250
251        let profile_key = message
252            .profile_key
253            .ok_or(ProvisioningError::MissingProfileKey)?;
254
255        // Optional in the proto (field 13 deprecated, field 15 modern).
256        // Moved out — partial move, the remaining field accesses below are
257        // independent. presage prefers AEP if both are present.
258        let account_entropy_pool = message
259            .account_entropy_pool
260            .map(|s| s.parse())
261            .transpose()?;
262        let ephemeral_backup_key = message
263            .ephemeral_backup_key
264            .map(|key| {
265                key.try_into().map_err(|key: Vec<u8>| {
266                    ProvisioningError::InvalidEphemeralBackupKey(key.len())
267                })
268            })
269            .transpose()?;
270
271        let phone_number = message
272            .number
273            .ok_or(ProvisioningError::MissingPhoneNumber)?;
274
275        let phone_number = phone_number
276            .parse::<E164>()
277            .map_err(ProvisioningError::InvalidPhoneNumber)?;
278
279        let provisioning_code = message
280            .provisioning_code
281            .ok_or(ProvisioningError::MissingProvisioningCode)?;
282
283        let aci_key_pair =
284            IdentityKeyPair::new(aci_public_key, aci_private_key);
285        let pni_key_pair =
286            IdentityKeyPair::new(pni_public_key, pni_private_key);
287
288        let (
289            _aci_pre_keys,
290            aci_signed_pre_key,
291            _aci_pq_pre_keys,
292            aci_pq_last_resort_pre_key,
293        ) = crate::pre_keys::replenish_pre_keys(
294            aci_store,
295            csprng,
296            &aci_key_pair,
297            true,
298            0,
299            0,
300        )
301        .await?;
302
303        let aci_pq_last_resort_pre_key =
304            aci_pq_last_resort_pre_key.expect("requested last resort key");
305        assert!(_aci_pre_keys.is_empty());
306        assert!(_aci_pq_pre_keys.is_empty());
307
308        let (
309            _pni_pre_keys,
310            pni_signed_pre_key,
311            _pni_pq_pre_keys,
312            pni_pq_last_resort_pre_key,
313        ) = crate::pre_keys::replenish_pre_keys(
314            pni_store,
315            csprng,
316            &pni_key_pair,
317            true,
318            0,
319            0,
320        )
321        .await?;
322
323        let pni_pq_last_resort_pre_key =
324            pni_pq_last_resort_pre_key.expect("requested last resort key");
325        assert!(_pni_pre_keys.is_empty());
326        assert!(_pni_pq_pre_keys.is_empty());
327
328        let encrypted_device_name = BASE64_RELAXED.encode(
329            encrypt_device_name(csprng, device_name, &aci_public_key)?
330                .encode_to_vec(),
331        );
332
333        let profile_key = ProfileKey::create(
334            profile_key
335                .as_slice()
336                .try_into()
337                .map_err(ProvisioningError::InvalidProfileKey)?,
338        );
339
340        let request = LinkRequest {
341            verification_code: provisioning_code,
342            account_attributes: LinkAccountAttributes {
343                registration_id,
344                pni_registration_id,
345                fetches_messages: true,
346                capabilities: LinkCapabilities::default(),
347                name: encrypted_device_name,
348            },
349            device_activation_request: DeviceActivationRequest {
350                aci_signed_pre_key: aci_signed_pre_key.try_into()?,
351                pni_signed_pre_key: pni_signed_pre_key.try_into()?,
352                aci_pq_last_resort_pre_key: aci_pq_last_resort_pre_key
353                    .try_into()?,
354                pni_pq_last_resort_pre_key: pni_pq_last_resort_pre_key
355                    .try_into()?,
356            },
357        };
358
359        let LinkResponse {
360            aci,
361            pni,
362            device_id,
363        } = push_service
364            .link_device(
365                &request,
366                HttpAuth {
367                    username: phone_number.to_string(),
368                    password: password.to_owned(),
369                },
370            )
371            .await?;
372
373        tx.send(SecondaryDeviceProvisioning::NewDeviceRegistration(
374            NewDeviceRegistration {
375                phone_number,
376                service_ids: ServiceIds { aci, pni },
377                device_id,
378                registration_id,
379                pni_registration_id,
380                aci_private_key,
381                aci_public_key,
382                pni_private_key,
383                pni_public_key,
384                profile_key,
385                account_entropy_pool,
386                ephemeral_backup_key,
387            },
388        ))
389        .await
390        .expect("failed to send provisioning message in rx channel");
391    } else {
392        return Err(ProvisioningError::InvalidStep);
393    }
394
395    Ok(())
396}