1use libsignal_protocol::IdentityKeyStore;
2use rand::{CryptoRng, Rng};
3use reqwest::Method;
4use serde::{Deserialize, Serialize};
5use tracing::Instrument;
6use uuid::Uuid;
7
8use super::ServiceError;
9use crate::{
10 pre_keys::{KyberPreKeyEntity, PreKeysStore, SignedPreKeyEntity},
11 provisioning::ProvisioningError,
12 push_service::response::{
13 error_mapper, json_or_unhandled, parse_retry_after,
14 SignalServiceResponse,
15 },
16 utils::{serde_base64, TryIntoE164},
17 websocket::{self, account::AccountAttributes, SignalWebSocket},
18};
19
20#[derive(derive_more::Debug, Clone, Serialize, Deserialize)]
23pub struct AuthCredentials {
24 pub username: String,
25 #[debug(ignore)]
26 pub password: String,
27}
28
29#[derive(Debug, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct RegistrationLockFailure {
32 pub length: Option<u32>,
33 pub time_remaining: Option<u64>,
34 #[serde(rename = "backup_credentials")]
35 pub svr1_credentials: Option<AuthCredentials>,
36 pub svr2_credentials: Option<AuthCredentials>,
37}
38
39#[derive(Debug, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct VerifyAccountResponse {
42 #[serde(rename = "uuid")]
43 pub aci: Uuid,
44 pub pni: Uuid,
45 pub storage_capable: bool,
46 #[serde(default)]
47 pub number: Option<String>,
48}
49
50#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
51#[serde(rename_all = "snake_case")]
52pub enum VerificationTransport {
53 Sms,
54 Voice,
55}
56
57#[derive(Clone, Debug)]
58pub enum RegistrationMethod<'a> {
59 SessionId(&'a str),
60 RecoveryPassword(&'a str),
61}
62
63impl<'a> RegistrationMethod<'a> {
64 pub fn session_id(&'a self) -> Option<&'a str> {
65 match self {
66 Self::SessionId(x) => Some(x),
67 _ => None,
68 }
69 }
70
71 pub fn recovery_password(&'a self) -> Option<&'a str> {
72 match self {
73 Self::RecoveryPassword(x) => Some(x),
74 _ => None,
75 }
76 }
77}
78
79#[derive(Debug, Serialize)]
80#[serde(rename_all = "camelCase")]
81pub struct RegistrationKeyPackage {
82 pub aci_identity_key: Vec<u8>,
83 pub pni_identity_key: Vec<u8>,
84 pub aci_signed_pre_key: SignedPreKeyEntity,
85 pub pni_signed_pre_key: SignedPreKeyEntity,
86 pub aci_pq_last_resort_pre_key: KyberPreKeyEntity,
87 pub pni_pq_last_resort_pre_key: KyberPreKeyEntity,
88}
89
90#[derive(Debug, Serialize)]
91#[serde(rename_all = "camelCase")]
92pub struct DeviceActivationRequest {
93 pub aci_signed_pre_key: SignedPreKeyEntity,
94 pub pni_signed_pre_key: SignedPreKeyEntity,
95 pub aci_pq_last_resort_pre_key: KyberPreKeyEntity,
96 pub pni_pq_last_resort_pre_key: KyberPreKeyEntity,
97}
98
99#[derive(Debug, Serialize)]
100#[serde(rename_all = "camelCase")]
101pub struct GcmRegistrationId<'a> {
102 pub gcm_registration_id: &'a str,
103 pub web_socket_channel: bool,
104}
105
106#[derive(Debug, Serialize)]
107pub struct CaptchaAttributes<'a> {
108 #[serde(rename = "type")]
109 pub challenge_type: &'a str,
110 pub token: &'a str,
111 pub captcha: &'a str,
112}
113
114#[derive(Debug, Clone, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct RegistrationSessionMetadataResponse {
117 pub id: String,
118 #[serde(default)]
119 pub next_sms: Option<i32>,
120 #[serde(default)]
121 pub next_call: Option<i32>,
122 #[serde(default)]
123 pub next_verification_attempt: Option<i32>,
124 pub allowed_to_request_code: bool,
125 #[serde(default)]
126 pub requested_information: Vec<String>,
127 pub verified: bool,
128}
129
130impl RegistrationSessionMetadataResponse {
131 pub fn push_challenge_required(&self) -> bool {
132 self.requested_information
134 .iter()
135 .any(|x| x.as_str() == "pushChallenge")
136 }
137
138 pub fn captcha_required(&self) -> bool {
139 self.requested_information
141 .iter()
142 .any(|x| x.as_str() == "captcha")
143 }
144}
145
146async fn session_rate_limited<R>(response: R) -> ServiceError
149where
150 R: SignalServiceResponse,
151 ServiceError: From<<R as SignalServiceResponse>::Error>,
152{
153 let retry_after =
154 response.header("retry-after").and_then(parse_retry_after);
155 match json_or_unhandled::<R, RegistrationSessionMetadataResponse>(response)
156 .await
157 {
158 Ok(session) => ServiceError::VerificationSessionRateLimited {
159 session,
160 retry_after,
161 },
162 Err(error) => error,
163 }
164}
165
166error_mapper! {
169 create_verification_session_errors:
170 TOO_MANY_REQUESTS => fn session_rate_limited,
172}
173
174error_mapper! {
177 patch_verification_session_errors:
178 FORBIDDEN => TokenNotAccepted(RegistrationSessionMetadataResponse),
180 NOT_FOUND => NoSuchSession,
182 UNPROCESSABLE_ENTITY => InvalidVerificationSessionId,
184 TOO_MANY_REQUESTS => fn session_rate_limited,
186}
187
188error_mapper! {
191 request_verification_code_errors:
192 NOT_FOUND => NoSuchSession,
194 CONFLICT => RegistrationSessionConflict(RegistrationSessionMetadataResponse),
196 IM_A_TEAPOT => InvalidTransportMode(RegistrationSessionMetadataResponse),
198 UNPROCESSABLE_ENTITY => InvalidVerificationSessionId,
200 TOO_MANY_REQUESTS => fn session_rate_limited,
202 440 => VerificationDeliveryFailed(crate::push_service::VerificationDeliveryFailure),
205}
206
207error_mapper! {
210 submit_verification_code_errors:
211 NOT_FOUND => NoSuchSession,
213 CONFLICT => RegistrationSessionConflict(RegistrationSessionMetadataResponse),
215 UNPROCESSABLE_ENTITY => InvalidVerificationSessionId,
217 TOO_MANY_REQUESTS => fn session_rate_limited,
219}
220
221error_mapper! {
224 post_registration_errors:
225 CONFLICT => DeviceTransferAvailable,
227}
228
229impl SignalWebSocket<websocket::Unidentified> {
230 pub async fn create_verification_session<'a>(
233 &mut self,
234 number: &'a str,
235 push_token: Option<&'a str>,
236 mcc: Option<&'a str>,
237 mnc: Option<&'a str>,
238 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
239 #[derive(serde::Serialize, Debug)]
240 #[serde(rename_all = "camelCase")]
241 struct VerificationSessionMetadataRequestBody<'a> {
242 number: &'a str,
243 push_token: Option<&'a str>,
244 mcc: Option<&'a str>,
245 mnc: Option<&'a str>,
246 push_token_type: Option<&'a str>,
247 }
248
249 self.http_request(Method::POST, "/v1/verification/session")?
250 .send_json(&VerificationSessionMetadataRequestBody {
251 number,
252 push_token_type: push_token.as_ref().map(|_| "fcm"),
253 push_token,
254 mcc,
255 mnc,
256 })
257 .await?
258 .service_error_for_status_with(create_verification_session_errors)
259 .await?
260 .json()
261 .await
262 }
263
264 pub async fn patch_verification_session<'a>(
265 &mut self,
266 session_id: &'a str,
267 push_token: Option<&'a str>,
268 mcc: Option<&'a str>,
269 mnc: Option<&'a str>,
270 captcha: Option<&'a str>,
271 push_challenge: Option<&'a str>,
272 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
273 #[derive(serde::Serialize, Debug)]
274 #[serde(rename_all = "camelCase")]
275 struct UpdateVerificationSessionRequestBody<'a> {
276 captcha: Option<&'a str>,
277 push_token: Option<&'a str>,
278 push_challenge: Option<&'a str>,
279 mcc: Option<&'a str>,
280 mnc: Option<&'a str>,
281 push_token_type: Option<&'a str>,
282 }
283
284 self.http_request(
285 Method::PATCH,
286 format!("/v1/verification/session/{}", session_id),
287 )?
288 .send_json(&UpdateVerificationSessionRequestBody {
289 captcha,
290 push_token_type: push_token.as_ref().map(|_| "fcm"),
291 push_token,
292 mcc,
293 mnc,
294 push_challenge,
295 })
296 .await?
297 .service_error_for_status_with(patch_verification_session_errors)
298 .await?
299 .json()
300 .await
301 }
302
303 pub async fn request_verification_code(
315 &mut self,
316 session_id: &str,
317 client: &str,
318 transport: VerificationTransport,
322 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
323 #[derive(Debug, Serialize)]
324 struct VerificationCodeRequest<'a> {
325 transport: VerificationTransport,
326 client: &'a str,
327 }
328
329 self.http_request(
330 Method::POST,
331 format!("/v1/verification/session/{}/code", session_id),
332 )?
333 .send_json(&VerificationCodeRequest { transport, client })
334 .await?
335 .service_error_for_status_with(request_verification_code_errors)
336 .await?
337 .json()
338 .await
339 }
340
341 #[allow(clippy::too_many_arguments)]
342 pub async fn submit_registration_request(
343 &mut self,
344 registration_method: RegistrationMethod<'_>,
345 gcm_token: Option<GcmRegistrationId<'_>>,
346 phonenumber: impl TryIntoE164,
347 password: &str,
348 account_attributes: AccountAttributes,
349 skip_device_transfer: bool,
350 keys: RegistrationKeyPackage,
351 ) -> Result<VerifyAccountResponse, ServiceError> {
352 #[derive(serde::Serialize, Debug)]
353 #[serde(rename_all = "camelCase")]
354 struct RegistrationSessionRequestBody<'a> {
356 session_id: Option<&'a str>,
357 recovery_password: Option<&'a str>,
358 account_attributes: AccountAttributes,
359 skip_device_transfer: bool,
360 #[serde(default, with = "serde_base64")]
361 pni_identity_key: Vec<u8>,
362 #[serde(default, with = "serde_base64")]
363 aci_identity_key: Vec<u8>,
364 aci_signed_pre_key: SignedPreKeyEntity,
365 pni_signed_pre_key: SignedPreKeyEntity,
366 aci_pq_last_resort_pre_key: KyberPreKeyEntity,
367 pni_pq_last_resort_pre_key: KyberPreKeyEntity,
368 gcm_token: Option<GcmRegistrationId<'a>>,
369 require_atomic: bool,
370 }
371
372 let phonenumber = phonenumber
373 .try_into_e164()
374 .map_err(|_| ServiceError::InvalidPhoneNumber)?;
375
376 self.http_request(Method::POST, "/v1/registration")?
377 .registration_auth_header(phonenumber, password)
378 .send_json(&RegistrationSessionRequestBody {
379 session_id: registration_method.session_id(),
380 recovery_password: registration_method.recovery_password(),
381 account_attributes,
382 skip_device_transfer,
383 aci_identity_key: keys.aci_identity_key,
384 pni_identity_key: keys.pni_identity_key,
385 aci_signed_pre_key: keys.aci_signed_pre_key,
386 pni_signed_pre_key: keys.pni_signed_pre_key,
387 aci_pq_last_resort_pre_key: keys.aci_pq_last_resort_pre_key,
388 pni_pq_last_resort_pre_key: keys.pni_pq_last_resort_pre_key,
389 gcm_token,
390 require_atomic: true, })
392 .await?
393 .service_error_for_status_with(post_registration_errors)
394 .await?
395 .json()
396 .await
397 }
398
399 pub async fn submit_verification_code(
400 &mut self,
401 session_id: &str,
402 verification_code: &str,
403 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
404 #[derive(Debug, Serialize)]
405 struct VerificationCode<'a> {
406 code: &'a str,
407 }
408
409 self.http_request(
410 Method::PUT,
411 format!("/v1/verification/session/{}/code", session_id),
412 )?
413 .send_json(&VerificationCode {
414 code: verification_code,
415 })
416 .await?
417 .service_error_for_status_with(submit_verification_code_errors)
418 .await?
419 .json()
420 .await
421 }
422
423 #[allow(clippy::too_many_arguments)]
424 pub async fn register_account<
425 R: Rng + CryptoRng,
426 Aci: PreKeysStore + IdentityKeyStore,
427 Pni: PreKeysStore + IdentityKeyStore,
428 >(
429 &mut self,
430 csprng: &mut R,
431 registration_method: RegistrationMethod<'_>,
432 gcm_token: Option<GcmRegistrationId<'_>>,
433 account_attributes: AccountAttributes,
434 aci_protocol_store: &mut Aci,
435 pni_protocol_store: &mut Pni,
436 skip_device_transfer: bool,
437 phonenumber: impl TryIntoE164,
438 password: &str,
439 ) -> Result<VerifyAccountResponse, ProvisioningError> {
440 let aci_identity_key_pair = aci_protocol_store
441 .get_identity_key_pair()
442 .instrument(tracing::trace_span!("get ACI identity key pair"))
443 .await?;
444 let pni_identity_key_pair = pni_protocol_store
445 .get_identity_key_pair()
446 .instrument(tracing::trace_span!("get PNI identity key pair"))
447 .await?;
448
449 let (
450 _aci_pre_keys,
451 aci_signed_pre_key,
452 _aci_kyber_pre_keys,
453 aci_last_resort_kyber_prekey,
454 ) = crate::pre_keys::replenish_pre_keys(
455 aci_protocol_store,
456 csprng,
457 &aci_identity_key_pair,
458 true,
459 0,
460 0,
461 )
462 .await?;
463
464 let (
465 _pni_pre_keys,
466 pni_signed_pre_key,
467 _pni_kyber_pre_keys,
468 pni_last_resort_kyber_prekey,
469 ) = crate::pre_keys::replenish_pre_keys(
470 pni_protocol_store,
471 csprng,
472 &pni_identity_key_pair,
473 true,
474 0,
475 0,
476 )
477 .await?;
478
479 let aci_identity_key = aci_identity_key_pair.identity_key();
480 let pni_identity_key = pni_identity_key_pair.identity_key();
481 let keys = RegistrationKeyPackage {
482 aci_identity_key: aci_identity_key.serialize().into(),
483 pni_identity_key: pni_identity_key.serialize().into(),
484 aci_signed_pre_key: SignedPreKeyEntity::try_from(
485 &aci_signed_pre_key,
486 )
487 .unwrap(),
488 pni_signed_pre_key: pni_signed_pre_key.try_into()?,
489 aci_pq_last_resort_pre_key: aci_last_resort_kyber_prekey
490 .expect("requested last resort prekey")
491 .try_into()?,
492 pni_pq_last_resort_pre_key: pni_last_resort_kyber_prekey
493 .expect("requested last resort prekey")
494 .try_into()?,
495 };
496
497 let result = self
498 .submit_registration_request(
499 registration_method,
500 gcm_token,
501 phonenumber,
502 password,
503 account_attributes,
504 skip_device_transfer,
505 keys,
506 )
507 .await?;
508
509 Ok(result)
510 }
511}