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}
104
105impl ProvisioningError {
106    pub fn invalid_public_key(e: impl Into<InvalidKeyError>) -> Self {
107        ProvisioningError::InvalidPublicKey(e.into())
108    }
109
110    pub fn invalid_private_key(e: impl Into<InvalidKeyError>) -> Self {
111        ProvisioningError::InvalidPrivateKey(e.into())
112    }
113}
114
115#[derive(Debug, thiserror::Error)]
116pub enum InvalidKeyError {
117    #[error("curve error: {0}")]
118    Curve(#[from] CurveError),
119    #[error("base64 decoding error: {0}")]
120    Base64(#[from] base64::DecodeError),
121    #[error("protocol error: {0}")]
122    Protocol(#[from] libsignal_protocol::SignalProtocolError),
123}
124
125pub fn generate_registration_id<R: rand::Rng + rand::CryptoRng>(
126    csprng: &mut R,
127) -> u32 {
128    csprng.random_range(1..16380)
129}
130
131#[derive(Debug, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct ConfirmCodeResponse {
134    pub uuid: Uuid,
135    pub storage_capable: bool,
136}
137
138#[derive(Debug)]
139#[allow(clippy::large_enum_variant)]
140pub enum SecondaryDeviceProvisioning {
141    Url(Url),
142    NewDeviceRegistration(NewDeviceRegistration),
143}
144
145#[derive(derive_more::Debug)]
146pub struct NewDeviceRegistration {
147    pub phone_number: libsignal_core::E164,
148    pub device_id: DeviceId,
149    pub registration_id: u32,
150    pub pni_registration_id: u32,
151    pub service_ids: ServiceIds,
152    #[debug(ignore)]
153    pub aci_private_key: PrivateKey,
154    pub aci_public_key: IdentityKey,
155    #[debug(ignore)]
156    pub pni_private_key: PrivateKey,
157    pub pni_public_key: IdentityKey,
158    #[debug(ignore)]
159    pub profile_key: ProfileKey,
160    /// Account Entropy Pool — the modern `accountEntropyPool` field 15.
161    /// 64-character alphanumeric string from which the canonical master
162    /// key is derived (`AccountEntropyPool::derive_svr_key`). When present,
163    /// it should be preferred over the deprecated `master_key` field.
164    #[debug(ignore)]
165    pub account_entropy_pool: Option<AccountEntropyPool>,
166}
167
168pub async fn link_device<
169    R: rand::Rng + rand::CryptoRng,
170    Aci: PreKeysStore,
171    Pni: PreKeysStore,
172>(
173    aci_store: &mut Aci,
174    pni_store: &mut Pni,
175    csprng: &mut R,
176    mut push_service: PushService,
177    password: &str,
178    device_name: &str,
179    mut tx: Sender<SecondaryDeviceProvisioning>,
180) -> Result<(), ProvisioningError> {
181    // open a websocket without authentication, to receive a tsurl://
182    let ws = push_service
183        .ws(
184            "/v1/websocket/provisioning/",
185            "/v1/keepalive/provisioning",
186            &[],
187            None,
188        )
189        .await?;
190
191    let registration_id = csprng.random_range(1..256);
192    let pni_registration_id = csprng.random_range(1..256);
193
194    let provisioning_pipe = ProvisioningPipe::from_socket(ws, csprng);
195    let provision_stream = provisioning_pipe.stream();
196    pin_mut!(provision_stream);
197
198    if let ProvisioningStep::Url(url) = provision_stream
199        .next()
200        .await
201        .ok_or(ProvisioningError::MissingUrl)??
202    {
203        tx.send(SecondaryDeviceProvisioning::Url(url))
204            .await
205            .expect("failed to send provisioning Url in channel");
206    } else {
207        return Err(ProvisioningError::InvalidStep);
208    }
209
210    if let ProvisioningStep::Message(message) =
211        provision_stream
212            .next()
213            .await
214            .ok_or(ProvisioningError::MissingMessage)??
215    {
216        let aci_public_key = PublicKey::deserialize(
217            &message
218                .aci_identity_key_public
219                .ok_or(ProvisioningError::MissingPublicKey)?,
220        )
221        .map_err(ProvisioningError::invalid_public_key)?;
222        let aci_public_key = IdentityKey::new(aci_public_key);
223
224        let aci_private_key = PrivateKey::deserialize(
225            &message
226                .aci_identity_key_private
227                .ok_or(ProvisioningError::MissingPrivateKey)?,
228        )
229        .map_err(ProvisioningError::invalid_private_key)?;
230
231        let pni_public_key = PublicKey::deserialize(
232            &message
233                .pni_identity_key_public
234                .ok_or(ProvisioningError::MissingPublicKey)?,
235        )
236        .map_err(ProvisioningError::invalid_public_key)?;
237        let pni_public_key = IdentityKey::new(pni_public_key);
238
239        let pni_private_key = PrivateKey::deserialize(
240            &message
241                .pni_identity_key_private
242                .ok_or(ProvisioningError::MissingPrivateKey)?,
243        )
244        .map_err(ProvisioningError::invalid_private_key)?;
245
246        let profile_key = message
247            .profile_key
248            .ok_or(ProvisioningError::MissingProfileKey)?;
249
250        // Optional in the proto (field 13 deprecated, field 15 modern).
251        // Moved out — partial move, the remaining field accesses below are
252        // independent. presage prefers AEP if both are present.
253        let account_entropy_pool = message
254            .account_entropy_pool
255            .map(|s| s.parse())
256            .transpose()?;
257
258        let phone_number = message
259            .number
260            .ok_or(ProvisioningError::MissingPhoneNumber)?;
261
262        let phone_number = phone_number
263            .parse::<E164>()
264            .map_err(ProvisioningError::InvalidPhoneNumber)?;
265
266        let provisioning_code = message
267            .provisioning_code
268            .ok_or(ProvisioningError::MissingProvisioningCode)?;
269
270        let aci_key_pair =
271            IdentityKeyPair::new(aci_public_key, aci_private_key);
272        let pni_key_pair =
273            IdentityKeyPair::new(pni_public_key, pni_private_key);
274
275        let (
276            _aci_pre_keys,
277            aci_signed_pre_key,
278            _aci_pq_pre_keys,
279            aci_pq_last_resort_pre_key,
280        ) = crate::pre_keys::replenish_pre_keys(
281            aci_store,
282            csprng,
283            &aci_key_pair,
284            true,
285            0,
286            0,
287        )
288        .await?;
289
290        let aci_pq_last_resort_pre_key =
291            aci_pq_last_resort_pre_key.expect("requested last resort key");
292        assert!(_aci_pre_keys.is_empty());
293        assert!(_aci_pq_pre_keys.is_empty());
294
295        let (
296            _pni_pre_keys,
297            pni_signed_pre_key,
298            _pni_pq_pre_keys,
299            pni_pq_last_resort_pre_key,
300        ) = crate::pre_keys::replenish_pre_keys(
301            pni_store,
302            csprng,
303            &pni_key_pair,
304            true,
305            0,
306            0,
307        )
308        .await?;
309
310        let pni_pq_last_resort_pre_key =
311            pni_pq_last_resort_pre_key.expect("requested last resort key");
312        assert!(_pni_pre_keys.is_empty());
313        assert!(_pni_pq_pre_keys.is_empty());
314
315        let encrypted_device_name = BASE64_RELAXED.encode(
316            encrypt_device_name(csprng, device_name, &aci_public_key)?
317                .encode_to_vec(),
318        );
319
320        let profile_key = ProfileKey::create(
321            profile_key
322                .as_slice()
323                .try_into()
324                .map_err(ProvisioningError::InvalidProfileKey)?,
325        );
326
327        let request = LinkRequest {
328            verification_code: provisioning_code,
329            account_attributes: LinkAccountAttributes {
330                registration_id,
331                pni_registration_id,
332                fetches_messages: true,
333                capabilities: LinkCapabilities::default(),
334                name: encrypted_device_name,
335            },
336            device_activation_request: DeviceActivationRequest {
337                aci_signed_pre_key: aci_signed_pre_key.try_into()?,
338                pni_signed_pre_key: pni_signed_pre_key.try_into()?,
339                aci_pq_last_resort_pre_key: aci_pq_last_resort_pre_key
340                    .try_into()?,
341                pni_pq_last_resort_pre_key: pni_pq_last_resort_pre_key
342                    .try_into()?,
343            },
344        };
345
346        let LinkResponse {
347            aci,
348            pni,
349            device_id,
350        } = push_service
351            .link_device(
352                &request,
353                HttpAuth {
354                    username: phone_number.to_string(),
355                    password: password.to_owned(),
356                },
357            )
358            .await?;
359
360        tx.send(SecondaryDeviceProvisioning::NewDeviceRegistration(
361            NewDeviceRegistration {
362                phone_number,
363                service_ids: ServiceIds { aci, pni },
364                device_id,
365                registration_id,
366                pni_registration_id,
367                aci_private_key,
368                aci_public_key,
369                pni_private_key,
370                pni_public_key,
371                profile_key,
372                account_entropy_pool,
373            },
374        ))
375        .await
376        .expect("failed to send provisioning message in rx channel");
377    } else {
378        return Err(ProvisioningError::InvalidStep);
379    }
380
381    Ok(())
382}