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 utils::{serde_base64, TryIntoE164},
13 websocket::{self, account::AccountAttributes, SignalWebSocket},
14};
15
16#[derive(derive_more::Debug, Clone, Serialize, Deserialize)]
19pub struct AuthCredentials {
20 pub username: String,
21 #[debug(ignore)]
22 pub password: String,
23}
24
25#[derive(Debug, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct RegistrationLockFailure {
28 pub length: Option<u32>,
29 pub time_remaining: Option<u64>,
30 #[serde(rename = "backup_credentials")]
31 pub svr1_credentials: Option<AuthCredentials>,
32 pub svr2_credentials: Option<AuthCredentials>,
33}
34
35#[derive(Debug, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct VerifyAccountResponse {
38 #[serde(rename = "uuid")]
39 pub aci: Uuid,
40 pub pni: Uuid,
41 pub storage_capable: bool,
42 #[serde(default)]
43 pub number: Option<String>,
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
47#[serde(rename_all = "snake_case")]
48pub enum VerificationTransport {
49 Sms,
50 Voice,
51}
52
53#[derive(Clone, Debug)]
54pub enum RegistrationMethod<'a> {
55 SessionId(&'a str),
56 RecoveryPassword(&'a str),
57}
58
59impl<'a> RegistrationMethod<'a> {
60 pub fn session_id(&'a self) -> Option<&'a str> {
61 match self {
62 Self::SessionId(x) => Some(x),
63 _ => None,
64 }
65 }
66
67 pub fn recovery_password(&'a self) -> Option<&'a str> {
68 match self {
69 Self::RecoveryPassword(x) => Some(x),
70 _ => None,
71 }
72 }
73}
74
75#[derive(Debug, Serialize)]
76#[serde(rename_all = "camelCase")]
77pub struct RegistrationKeyPackage {
78 pub aci_identity_key: Vec<u8>,
79 pub pni_identity_key: Vec<u8>,
80 pub aci_signed_pre_key: SignedPreKeyEntity,
81 pub pni_signed_pre_key: SignedPreKeyEntity,
82 pub aci_pq_last_resort_pre_key: KyberPreKeyEntity,
83 pub pni_pq_last_resort_pre_key: KyberPreKeyEntity,
84}
85
86#[derive(Debug, Serialize)]
87#[serde(rename_all = "camelCase")]
88pub struct DeviceActivationRequest {
89 pub aci_signed_pre_key: SignedPreKeyEntity,
90 pub pni_signed_pre_key: SignedPreKeyEntity,
91 pub aci_pq_last_resort_pre_key: KyberPreKeyEntity,
92 pub pni_pq_last_resort_pre_key: KyberPreKeyEntity,
93}
94
95#[derive(Debug, Serialize)]
96#[serde(rename_all = "camelCase")]
97pub struct GcmRegistrationId<'a> {
98 pub gcm_registration_id: &'a str,
99 pub web_socket_channel: bool,
100}
101
102#[derive(Debug, Serialize)]
103pub struct CaptchaAttributes<'a> {
104 #[serde(rename = "type")]
105 pub challenge_type: &'a str,
106 pub token: &'a str,
107 pub captcha: &'a str,
108}
109
110#[derive(Debug, Clone, Deserialize)]
111#[serde(rename_all = "camelCase")]
112pub struct RegistrationSessionMetadataResponse {
113 pub id: String,
114 #[serde(default)]
115 pub next_sms: Option<i32>,
116 #[serde(default)]
117 pub next_call: Option<i32>,
118 #[serde(default)]
119 pub next_verification_attempt: Option<i32>,
120 pub allowed_to_request_code: bool,
121 #[serde(default)]
122 pub requested_information: Vec<String>,
123 pub verified: bool,
124}
125
126impl RegistrationSessionMetadataResponse {
127 pub fn push_challenge_required(&self) -> bool {
128 self.requested_information
130 .iter()
131 .any(|x| x.as_str() == "pushChallenge")
132 }
133
134 pub fn captcha_required(&self) -> bool {
135 self.requested_information
137 .iter()
138 .any(|x| x.as_str() == "captcha")
139 }
140}
141
142impl SignalWebSocket<websocket::Unidentified> {
143 pub async fn create_verification_session<'a>(
146 &mut self,
147 number: &'a str,
148 push_token: Option<&'a str>,
149 mcc: Option<&'a str>,
150 mnc: Option<&'a str>,
151 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
152 #[derive(serde::Serialize, Debug)]
153 #[serde(rename_all = "camelCase")]
154 struct VerificationSessionMetadataRequestBody<'a> {
155 number: &'a str,
156 push_token: Option<&'a str>,
157 mcc: Option<&'a str>,
158 mnc: Option<&'a str>,
159 push_token_type: Option<&'a str>,
160 }
161
162 self.http_request(Method::POST, "/v1/verification/session")?
163 .send_json(&VerificationSessionMetadataRequestBody {
164 number,
165 push_token_type: push_token.as_ref().map(|_| "fcm"),
166 push_token,
167 mcc,
168 mnc,
169 })
170 .await?
171 .service_error_for_status()
172 .await?
173 .json()
174 .await
175 }
176
177 pub async fn patch_verification_session<'a>(
178 &mut self,
179 session_id: &'a str,
180 push_token: Option<&'a str>,
181 mcc: Option<&'a str>,
182 mnc: Option<&'a str>,
183 captcha: Option<&'a str>,
184 push_challenge: Option<&'a str>,
185 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
186 #[derive(serde::Serialize, Debug)]
187 #[serde(rename_all = "camelCase")]
188 struct UpdateVerificationSessionRequestBody<'a> {
189 captcha: Option<&'a str>,
190 push_token: Option<&'a str>,
191 push_challenge: Option<&'a str>,
192 mcc: Option<&'a str>,
193 mnc: Option<&'a str>,
194 push_token_type: Option<&'a str>,
195 }
196
197 self.http_request(
198 Method::PATCH,
199 format!("/v1/verification/session/{}", session_id),
200 )?
201 .send_json(&UpdateVerificationSessionRequestBody {
202 captcha,
203 push_token_type: push_token.as_ref().map(|_| "fcm"),
204 push_token,
205 mcc,
206 mnc,
207 push_challenge,
208 })
209 .await?
210 .service_error_for_status()
211 .await?
212 .json()
213 .await
214 }
215
216 pub async fn request_verification_code(
228 &mut self,
229 session_id: &str,
230 client: &str,
231 transport: VerificationTransport,
235 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
236 #[derive(Debug, Serialize)]
237 struct VerificationCodeRequest<'a> {
238 transport: VerificationTransport,
239 client: &'a str,
240 }
241
242 self.http_request(
243 Method::POST,
244 format!("/v1/verification/session/{}/code", session_id),
245 )?
246 .send_json(&VerificationCodeRequest { transport, client })
247 .await?
248 .service_error_for_status()
249 .await?
250 .json()
251 .await
252 }
253
254 #[allow(clippy::too_many_arguments)]
255 pub async fn submit_registration_request(
256 &mut self,
257 registration_method: RegistrationMethod<'_>,
258 gcm_token: Option<GcmRegistrationId<'_>>,
259 phonenumber: impl TryIntoE164,
260 password: &str,
261 account_attributes: AccountAttributes,
262 skip_device_transfer: bool,
263 keys: RegistrationKeyPackage,
264 ) -> Result<VerifyAccountResponse, ServiceError> {
265 #[derive(serde::Serialize, Debug)]
266 #[serde(rename_all = "camelCase")]
267 struct RegistrationSessionRequestBody<'a> {
269 session_id: Option<&'a str>,
270 recovery_password: Option<&'a str>,
271 account_attributes: AccountAttributes,
272 skip_device_transfer: bool,
273 #[serde(default, with = "serde_base64")]
274 pni_identity_key: Vec<u8>,
275 #[serde(default, with = "serde_base64")]
276 aci_identity_key: Vec<u8>,
277 aci_signed_pre_key: SignedPreKeyEntity,
278 pni_signed_pre_key: SignedPreKeyEntity,
279 aci_pq_last_resort_pre_key: KyberPreKeyEntity,
280 pni_pq_last_resort_pre_key: KyberPreKeyEntity,
281 gcm_token: Option<GcmRegistrationId<'a>>,
282 require_atomic: bool,
283 }
284
285 let phonenumber = phonenumber
286 .try_into_e164()
287 .map_err(|_| ServiceError::InvalidPhoneNumber)?;
288
289 self.http_request(Method::POST, "/v1/registration")?
290 .registration_auth_header(phonenumber, password)
291 .send_json(&RegistrationSessionRequestBody {
292 session_id: registration_method.session_id(),
293 recovery_password: registration_method.recovery_password(),
294 account_attributes,
295 skip_device_transfer,
296 aci_identity_key: keys.aci_identity_key,
297 pni_identity_key: keys.pni_identity_key,
298 aci_signed_pre_key: keys.aci_signed_pre_key,
299 pni_signed_pre_key: keys.pni_signed_pre_key,
300 aci_pq_last_resort_pre_key: keys.aci_pq_last_resort_pre_key,
301 pni_pq_last_resort_pre_key: keys.pni_pq_last_resort_pre_key,
302 gcm_token,
303 require_atomic: true, })
305 .await?
306 .service_error_for_status()
307 .await?
308 .json()
309 .await
310 }
311
312 pub async fn submit_verification_code(
313 &mut self,
314 session_id: &str,
315 verification_code: &str,
316 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
317 #[derive(Debug, Serialize)]
318 struct VerificationCode<'a> {
319 code: &'a str,
320 }
321
322 self.http_request(
323 Method::PUT,
324 format!("/v1/verification/session/{}/code", session_id),
325 )?
326 .send_json(&VerificationCode {
327 code: verification_code,
328 })
329 .await?
330 .service_error_for_status()
331 .await?
332 .json()
333 .await
334 }
335
336 #[allow(clippy::too_many_arguments)]
337 pub async fn register_account<
338 R: Rng + CryptoRng,
339 Aci: PreKeysStore + IdentityKeyStore,
340 Pni: PreKeysStore + IdentityKeyStore,
341 >(
342 &mut self,
343 csprng: &mut R,
344 registration_method: RegistrationMethod<'_>,
345 gcm_token: Option<GcmRegistrationId<'_>>,
346 account_attributes: AccountAttributes,
347 aci_protocol_store: &mut Aci,
348 pni_protocol_store: &mut Pni,
349 skip_device_transfer: bool,
350 phonenumber: impl TryIntoE164,
351 password: &str,
352 ) -> Result<VerifyAccountResponse, ProvisioningError> {
353 let aci_identity_key_pair = aci_protocol_store
354 .get_identity_key_pair()
355 .instrument(tracing::trace_span!("get ACI identity key pair"))
356 .await?;
357 let pni_identity_key_pair = pni_protocol_store
358 .get_identity_key_pair()
359 .instrument(tracing::trace_span!("get PNI identity key pair"))
360 .await?;
361
362 let (
363 _aci_pre_keys,
364 aci_signed_pre_key,
365 _aci_kyber_pre_keys,
366 aci_last_resort_kyber_prekey,
367 ) = crate::pre_keys::replenish_pre_keys(
368 aci_protocol_store,
369 csprng,
370 &aci_identity_key_pair,
371 true,
372 0,
373 0,
374 )
375 .await?;
376
377 let (
378 _pni_pre_keys,
379 pni_signed_pre_key,
380 _pni_kyber_pre_keys,
381 pni_last_resort_kyber_prekey,
382 ) = crate::pre_keys::replenish_pre_keys(
383 pni_protocol_store,
384 csprng,
385 &pni_identity_key_pair,
386 true,
387 0,
388 0,
389 )
390 .await?;
391
392 let aci_identity_key = aci_identity_key_pair.identity_key();
393 let pni_identity_key = pni_identity_key_pair.identity_key();
394 let keys = RegistrationKeyPackage {
395 aci_identity_key: aci_identity_key.serialize().into(),
396 pni_identity_key: pni_identity_key.serialize().into(),
397 aci_signed_pre_key: SignedPreKeyEntity::try_from(
398 &aci_signed_pre_key,
399 )
400 .unwrap(),
401 pni_signed_pre_key: pni_signed_pre_key.try_into()?,
402 aci_pq_last_resort_pre_key: aci_last_resort_kyber_prekey
403 .expect("requested last resort prekey")
404 .try_into()?,
405 pni_pq_last_resort_pre_key: pni_last_resort_kyber_prekey
406 .expect("requested last resort prekey")
407 .try_into()?,
408 };
409
410 let result = self
411 .submit_registration_request(
412 registration_method,
413 gcm_token,
414 phonenumber,
415 password,
416 account_attributes,
417 skip_device_transfer,
418 keys,
419 )
420 .await?;
421
422 Ok(result)
423 }
424}