From 8070b8b14fda29644300440387542b99507813a5 Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Fri, 17 Dec 2021 22:26:50 +0100 Subject: [PATCH] Block WebAPI during active registration --- ts/textsecure/AccountManager.ts | 81 +++++++++++++++++++-------------- ts/textsecure/WebAPI.ts | 48 +++++++++++++++++++ ts/util/explodePromise.ts | 6 ++- 3 files changed, 100 insertions(+), 35 deletions(-) diff --git a/ts/textsecure/AccountManager.ts b/ts/textsecure/AccountManager.ts index c59d37eb9..72f2dc56d 100644 --- a/ts/textsecure/AccountManager.ts +++ b/ts/textsecure/AccountManager.ts @@ -172,24 +172,32 @@ export default class AccountManager extends EventTarget { const profileKey = getRandomBytes(PROFILE_KEY_LENGTH); const accessKey = deriveAccessKey(profileKey); - await this.createAccount({ - number, - verificationCode, - identityKeyPair, - pniKeyPair, - profileKey, - accessKey, - }); + const registrationBaton = this.server.startRegistration(); + try { + await this.createAccount({ + number, + verificationCode, + identityKeyPair, + pniKeyPair, + profileKey, + accessKey, + }); - await this.clearSessionsAndPreKeys(); + await this.clearSessionsAndPreKeys(); - await Promise.all( - [UUIDKind.ACI, UUIDKind.PNI].map(async kind => { - const keys = await this.generateKeys(SIGNED_KEY_GEN_BATCH_SIZE, kind); - await this.server.registerKeys(keys, kind); - await this.confirmKeys(keys, kind); - }) - ); + await Promise.all( + [UUIDKind.ACI, UUIDKind.PNI].map(async kind => { + const keys = await this.generateKeys( + SIGNED_KEY_GEN_BATCH_SIZE, + kind + ); + await this.server.registerKeys(keys, kind); + await this.confirmKeys(keys, kind); + }) + ); + } finally { + this.server.finishRegistration(registrationBaton); + } await this.registrationDone(); }); } @@ -276,23 +284,30 @@ export default class AccountManager extends EventTarget { ); } - await this.createAccount({ - number: provisionMessage.number, - verificationCode: provisionMessage.provisioningCode, - identityKeyPair: provisionMessage.identityKeyPair, - profileKey: provisionMessage.profileKey, - deviceName, - userAgent: provisionMessage.userAgent, - readReceipts: provisionMessage.readReceipts, - }); - await clearSessionsAndPreKeys(); - // TODO: DESKTOP-2794 - const keys = await this.generateKeys( - SIGNED_KEY_GEN_BATCH_SIZE, - UUIDKind.ACI - ); - await this.server.registerKeys(keys, UUIDKind.ACI); - await this.confirmKeys(keys, UUIDKind.ACI); + const registrationBaton = this.server.startRegistration(); + + try { + await this.createAccount({ + number: provisionMessage.number, + verificationCode: provisionMessage.provisioningCode, + identityKeyPair: provisionMessage.identityKeyPair, + profileKey: provisionMessage.profileKey, + deviceName, + userAgent: provisionMessage.userAgent, + readReceipts: provisionMessage.readReceipts, + }); + await clearSessionsAndPreKeys(); + // TODO: DESKTOP-2794 + const keys = await this.generateKeys( + SIGNED_KEY_GEN_BATCH_SIZE, + UUIDKind.ACI + ); + await this.server.registerKeys(keys, UUIDKind.ACI); + await this.confirmKeys(keys, UUIDKind.ACI); + } finally { + this.server.finishRegistration(registrationBaton); + } + await this.registrationDone(); }); } diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 38b3acade..0899a36ff 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -28,6 +28,8 @@ import type { Readable } from 'stream'; import { assert, strictAssert } from '../util/assert'; import { isRecord } from '../util/isRecord'; import * as durations from '../util/durations'; +import type { ExplodePromiseResultType } from '../util/explodePromise'; +import { explodePromise } from '../util/explodePromise'; import { getUserAgent } from '../util/getUserAgent'; import { getStreamWithTimeout } from '../util/getStreamWithTimeout'; import { formatAcceptLanguageHeader } from '../util/userLanguages'; @@ -633,6 +635,7 @@ type AjaxOptionsType = { urlParameters?: string; username?: string; validateResponse?: any; + isRegistration?: true; } & ( | { unauthenticated?: false; @@ -766,6 +769,8 @@ export type GetUuidsForE164sV2OptionsType = Readonly<{ }>; export type WebAPIType = { + startRegistration(): unknown; + finishRegistration(baton: unknown): void; confirmCode: ( number: string, code: string, @@ -1067,6 +1072,8 @@ export function initialize({ const PARSE_GROUP_LOG_RANGE_HEADER = /$versions (\d{1,10})-(\d{1,10})\/(d{1,10})/; + let activeRegistration: ExplodePromiseResultType | undefined; + const socketManager = new SocketManager({ url, certificateAuthority, @@ -1117,6 +1124,7 @@ export function initialize({ confirmCode, createGroup, deleteUsername, + finishRegistration, fetchLinkPreviewImage, fetchLinkPreviewMetadata, getAttachment, @@ -1165,6 +1173,7 @@ export function initialize({ sendMessagesUnauth, sendWithSenderKey, setSignedPreKey, + startRegistration, updateDeviceName, uploadAvatar, uploadGroupAvatar, @@ -1186,6 +1195,18 @@ export function initialize({ ): Promise; async function _ajax(param: AjaxOptionsType): Promise { + if ( + !param.unauthenticated && + activeRegistration && + !param.isRegistration + ) { + log.info('WebAPI: request blocked by active registration'); + const start = Date.now(); + await activeRegistration.promise; + const duration = Date.now() - start; + log.info(`WebAPI: request unblocked after ${duration}ms`); + } + if (!param.urlParameters) { param.urlParameters = ''; } @@ -1635,6 +1656,31 @@ export function initialize({ } } + function startRegistration() { + strictAssert( + activeRegistration === undefined, + 'Registration already in progress' + ); + + activeRegistration = explodePromise(); + log.info('WebAPI: starting registration'); + + return activeRegistration; + } + + function finishRegistration(registration: unknown) { + strictAssert(activeRegistration !== undefined, 'No active registration'); + strictAssert( + activeRegistration === registration, + 'Invalid registration baton' + ); + + log.info('WebAPI: finishing registration'); + const current = activeRegistration; + activeRegistration = undefined; + current.resolve(); + } + async function confirmCode( number: string, code: string, @@ -1677,6 +1723,7 @@ export function initialize({ password = newPassword; const response = (await _ajax({ + isRegistration: true, call, httpType: 'PUT', responseType: 'json', @@ -1749,6 +1796,7 @@ export function initialize({ }; await _ajax({ + isRegistration: true, call: 'keys', urlParameters: `?${uuidKindToQuery(uuidKind)}`, httpType: 'PUT', diff --git a/ts/util/explodePromise.ts b/ts/util/explodePromise.ts index 54867519f..2279019d9 100644 --- a/ts/util/explodePromise.ts +++ b/ts/util/explodePromise.ts @@ -1,11 +1,13 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -export function explodePromise(): { +export type ExplodePromiseResultType = Readonly<{ promise: Promise; resolve: (value: T) => void; reject: (error: Error) => void; -} { +}>; + +export function explodePromise(): ExplodePromiseResultType { let resolve: (value: T) => void; let reject: (error: Error) => void;