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::master_key::MasterKey;
28use crate::messagepipe::ServiceCredentials;
29use crate::prelude::ServiceError;
30use crate::push_service::linking::{
31    LinkAccountAttributes, LinkCapabilities, LinkRequest, LinkResponse,
32};
33use crate::utils::BASE64_RELAXED;
34use crate::websocket::registration::DeviceActivationRequest;
35use crate::{
36    account_manager::encrypt_device_name,
37    pre_keys::PreKeysStore,
38    push_service::{HttpAuth, PushService, ServiceIds},
39};
40
41pub use crate::proto::{
42    ProvisionEnvelope, ProvisionMessage, ProvisioningVersion,
43};
44
45pub struct ProvisioningSecrets {
46    pub credentials: ServiceCredentials,
47    pub master_key: Option<MasterKey>,
48    pub ephemeral_backup_key: Option<[u8; 32]>,
49    pub account_entropy_pool: AccountEntropyPool,
50    pub media_root_backup_key: Option<[u8; 32]>,
51}
52
53#[derive(thiserror::Error, Debug)]
54pub enum ProvisioningError {
55    #[error("no provisioning URL received")]
56    MissingUrl,
57    #[error("bad version number (unsupported)")]
58    BadVersionNumber,
59    #[error("missing public key")]
60    MissingPublicKey,
61    #[error("missing private key")]
62    MissingPrivateKey,
63    #[error("invalid public key")]
64    InvalidPublicKey(InvalidKeyError),
65    #[error("invalid privat key")]
66    InvalidPrivateKey(InvalidKeyError),
67    #[error("missing UUID")]
68    MissingUuid,
69    #[error("no provisioning message received")]
70    MissingMessage,
71    #[error("missing profile key")]
72    MissingProfileKey,
73    #[error("missing phone number")]
74    MissingPhoneNumber,
75    #[error("invalid phone number: {0}")]
76    InvalidPhoneNumber(<E164 as std::str::FromStr>::Err),
77    #[error("missing provisioning code")]
78    MissingProvisioningCode,
79    #[error("mismatched MAC")]
80    MismatchedMac,
81    #[error("AES CBC padding error: {0}")]
82    AesPaddingError(aes::cipher::block_padding::UnpadError),
83
84    #[error("invalid provisioning step received")]
85    InvalidStep,
86
87    #[error("Protobuf decoding error: {0}")]
88    DecodeError(#[from] prost::DecodeError),
89    #[error("Websocket error: {reason}")]
90    WsError { reason: String },
91    #[error("Websocket closing")]
92    WsClosing,
93    #[error("Service error: {0}")]
94    ServiceError(#[from] ServiceError),
95    #[error("libsignal-protocol error: {0}")]
96    ProtocolError(#[from] libsignal_protocol::SignalProtocolError),
97    #[error("invalid device ID: {0}")]
98    InvalidDeviceId(#[from] libsignal_core::InvalidDeviceId),
99    #[error("ProvisioningCipher in encrypt-only mode")]
100    EncryptOnlyProvisioningCipher,
101    #[error("invalid profile key bytes")]
102    InvalidProfileKey(TryFromSliceError),
103    #[error("invalid account entropy pool: {0}")]
104    InvalidAccountEntropyPool(#[from] InvalidAccountEntropyPool),
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 master key — the deprecated `masterKey` field 13 of
163    /// `ProvisionMessage`. Required by linked devices for legacy state
164    /// (Storage Service, KBS) and still sent by primary devices. May be
165    /// absent if the primary is on a build that has fully migrated to
166    /// `account_entropy_pool` only.
167    #[debug(ignore)]
168    pub master_key: Option<Vec<u8>>,
169    /// Account Entropy Pool — the modern `accountEntropyPool` field 15.
170    /// 64-character alphanumeric string from which the canonical master
171    /// key is derived (`AccountEntropyPool::derive_svr_key`). When present,
172    /// it should be preferred over the deprecated `master_key` field.
173    #[debug(ignore)]
174    pub account_entropy_pool: Option<AccountEntropyPool>,
175}
176
177pub async fn link_device<
178    R: rand::Rng + rand::CryptoRng,
179    Aci: PreKeysStore,
180    Pni: PreKeysStore,
181>(
182    aci_store: &mut Aci,
183    pni_store: &mut Pni,
184    csprng: &mut R,
185    mut push_service: PushService,
186    password: &str,
187    device_name: &str,
188    mut tx: Sender<SecondaryDeviceProvisioning>,
189) -> Result<(), ProvisioningError> {
190    // open a websocket without authentication, to receive a tsurl://
191    let ws = push_service
192        .ws(
193            "/v1/websocket/provisioning/",
194            "/v1/keepalive/provisioning",
195            &[],
196            None,
197        )
198        .await?;
199
200    let registration_id = csprng.random_range(1..256);
201    let pni_registration_id = csprng.random_range(1..256);
202
203    let provisioning_pipe = ProvisioningPipe::from_socket(ws, csprng);
204    let provision_stream = provisioning_pipe.stream();
205    pin_mut!(provision_stream);
206
207    if let ProvisioningStep::Url(url) = provision_stream
208        .next()
209        .await
210        .ok_or(ProvisioningError::MissingUrl)??
211    {
212        tx.send(SecondaryDeviceProvisioning::Url(url))
213            .await
214            .expect("failed to send provisioning Url in channel");
215    } else {
216        return Err(ProvisioningError::InvalidStep);
217    }
218
219    if let ProvisioningStep::Message(message) =
220        provision_stream
221            .next()
222            .await
223            .ok_or(ProvisioningError::MissingMessage)??
224    {
225        let aci_public_key = PublicKey::deserialize(
226            &message
227                .aci_identity_key_public
228                .ok_or(ProvisioningError::MissingPublicKey)?,
229        )
230        .map_err(ProvisioningError::invalid_public_key)?;
231        let aci_public_key = IdentityKey::new(aci_public_key);
232
233        let aci_private_key = PrivateKey::deserialize(
234            &message
235                .aci_identity_key_private
236                .ok_or(ProvisioningError::MissingPrivateKey)?,
237        )
238        .map_err(ProvisioningError::invalid_private_key)?;
239
240        let pni_public_key = PublicKey::deserialize(
241            &message
242                .pni_identity_key_public
243                .ok_or(ProvisioningError::MissingPublicKey)?,
244        )
245        .map_err(ProvisioningError::invalid_public_key)?;
246        let pni_public_key = IdentityKey::new(pni_public_key);
247
248        let pni_private_key = PrivateKey::deserialize(
249            &message
250                .pni_identity_key_private
251                .ok_or(ProvisioningError::MissingPrivateKey)?,
252        )
253        .map_err(ProvisioningError::invalid_private_key)?;
254
255        let profile_key = message
256            .profile_key
257            .ok_or(ProvisioningError::MissingProfileKey)?;
258
259        // Optional in the proto (field 13 deprecated, field 15 modern).
260        // Moved out — partial move, the remaining field accesses below are
261        // independent. presage prefers AEP if both are present.
262        let master_key = message.master_key;
263        let account_entropy_pool = message
264            .account_entropy_pool
265            .map(|s| s.parse())
266            .transpose()?;
267
268        let phone_number = message
269            .number
270            .ok_or(ProvisioningError::MissingPhoneNumber)?;
271
272        let phone_number = phone_number
273            .parse::<E164>()
274            .map_err(ProvisioningError::InvalidPhoneNumber)?;
275
276        let provisioning_code = message
277            .provisioning_code
278            .ok_or(ProvisioningError::MissingProvisioningCode)?;
279
280        let aci_key_pair =
281            IdentityKeyPair::new(aci_public_key, aci_private_key);
282        let pni_key_pair =
283            IdentityKeyPair::new(pni_public_key, pni_private_key);
284
285        let (
286            _aci_pre_keys,
287            aci_signed_pre_key,
288            _aci_pq_pre_keys,
289            aci_pq_last_resort_pre_key,
290        ) = crate::pre_keys::replenish_pre_keys(
291            aci_store,
292            csprng,
293            &aci_key_pair,
294            true,
295            0,
296            0,
297        )
298        .await?;
299
300        let aci_pq_last_resort_pre_key =
301            aci_pq_last_resort_pre_key.expect("requested last resort key");
302        assert!(_aci_pre_keys.is_empty());
303        assert!(_aci_pq_pre_keys.is_empty());
304
305        let (
306            _pni_pre_keys,
307            pni_signed_pre_key,
308            _pni_pq_pre_keys,
309            pni_pq_last_resort_pre_key,
310        ) = crate::pre_keys::replenish_pre_keys(
311            pni_store,
312            csprng,
313            &pni_key_pair,
314            true,
315            0,
316            0,
317        )
318        .await?;
319
320        let pni_pq_last_resort_pre_key =
321            pni_pq_last_resort_pre_key.expect("requested last resort key");
322        assert!(_pni_pre_keys.is_empty());
323        assert!(_pni_pq_pre_keys.is_empty());
324
325        let encrypted_device_name = BASE64_RELAXED.encode(
326            encrypt_device_name(csprng, device_name, &aci_public_key)?
327                .encode_to_vec(),
328        );
329
330        let profile_key = ProfileKey::create(
331            profile_key
332                .as_slice()
333                .try_into()
334                .map_err(ProvisioningError::InvalidProfileKey)?,
335        );
336
337        let request = LinkRequest {
338            verification_code: provisioning_code,
339            account_attributes: LinkAccountAttributes {
340                registration_id,
341                pni_registration_id,
342                fetches_messages: true,
343                capabilities: LinkCapabilities::default(),
344                name: encrypted_device_name,
345            },
346            device_activation_request: DeviceActivationRequest {
347                aci_signed_pre_key: aci_signed_pre_key.try_into()?,
348                pni_signed_pre_key: pni_signed_pre_key.try_into()?,
349                aci_pq_last_resort_pre_key: aci_pq_last_resort_pre_key
350                    .try_into()?,
351                pni_pq_last_resort_pre_key: pni_pq_last_resort_pre_key
352                    .try_into()?,
353            },
354        };
355
356        let LinkResponse {
357            aci,
358            pni,
359            device_id,
360        } = push_service
361            .link_device(
362                &request,
363                HttpAuth {
364                    username: phone_number.to_string(),
365                    password: password.to_owned(),
366                },
367            )
368            .await?;
369
370        tx.send(SecondaryDeviceProvisioning::NewDeviceRegistration(
371            NewDeviceRegistration {
372                phone_number,
373                service_ids: ServiceIds { aci, pni },
374                device_id,
375                registration_id,
376                pni_registration_id,
377                aci_private_key,
378                aci_public_key,
379                pni_private_key,
380                pni_public_key,
381                profile_key,
382                master_key,
383                account_entropy_pool,
384            },
385        ))
386        .await
387        .expect("failed to send provisioning message in rx channel");
388    } else {
389        return Err(ProvisioningError::InvalidStep);
390    }
391
392    Ok(())
393}