diff --git a/ts/background.ts b/ts/background.ts index 4b44f0f61..c0d433224 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -8,6 +8,7 @@ import { getTitleBarVisibility, TitleBarVisibility } from './types/Settings'; import { isWindowDragElement } from './util/isWindowDragElement'; import { assert } from './util/assert'; import * as refreshSenderCertificate from './refreshSenderCertificate'; +import { SenderCertificateMode } from './metadata/SecretSessionCipher'; import { routineProfileRefresh } from './routineProfileRefresh'; import { isMoreRecentThan, isOlderThan } from './util/timestamp'; @@ -2069,11 +2070,17 @@ export async function startApp(): Promise { window.Whisper.events, newVersion ); - refreshSenderCertificate.initialize({ - events: window.Whisper.events, - storage: window.storage, - navigator, - }); + + [SenderCertificateMode.WithE164, SenderCertificateMode.WithoutE164].forEach( + mode => { + refreshSenderCertificate.initialize({ + events: window.Whisper.events, + storage: window.storage, + mode, + navigator, + }); + } + ); window.Whisper.deliveryReceiptQueue.start(); window.Whisper.Notifications.enable(); diff --git a/ts/metadata/SecretSessionCipher.ts b/ts/metadata/SecretSessionCipher.ts index a092d0e5b..89c5e7abd 100644 --- a/ts/metadata/SecretSessionCipher.ts +++ b/ts/metadata/SecretSessionCipher.ts @@ -39,6 +39,11 @@ type ValidatorType = { ): Promise; }; +export const enum SenderCertificateMode { + WithE164, + WithoutE164, +} + export type SerializedCertificateType = { serialized: ArrayBuffer; }; diff --git a/ts/refreshSenderCertificate.ts b/ts/refreshSenderCertificate.ts index 1f2157b9b..51eed0dea 100644 --- a/ts/refreshSenderCertificate.ts +++ b/ts/refreshSenderCertificate.ts @@ -1,14 +1,24 @@ // Copyright 2018-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { once } from 'lodash'; import * as log from './logging/log'; +import { missingCaseError } from './util/missingCaseError'; +import { SenderCertificateMode } from './metadata/SecretSessionCipher'; const ONE_DAY = 24 * 60 * 60 * 1000; // one day const MINIMUM_TIME_LEFT = 2 * 60 * 60 * 1000; // two hours let timeout: null | ReturnType = null; let scheduledTime: null | number = null; -let scheduleNext: null | (() => void) = null; + +const removeOldKey = once((storage: typeof window.storage) => { + const oldCertKey = 'senderCertificateWithUuid'; + const oldUuidCert = storage.get(oldCertKey); + if (oldUuidCert) { + storage.remove(oldCertKey); + } +}); // We need to refresh our own profile regularly to account for newly-added devices which // do not support unidentified delivery. @@ -22,28 +32,39 @@ function refreshOurProfile() { export function initialize({ events, storage, + mode, navigator, }: Readonly<{ events: { on: (name: string, callback: () => void) => void; }; storage: typeof window.storage; + mode: SenderCertificateMode; navigator: Navigator; }>): void { - // We don't want to set up all of the below functions, but we do want to ensure that our - // refresh timer is up-to-date. - if (scheduleNext) { - scheduleNext(); - return; + let storageKey: 'senderCertificate' | 'senderCertificateNoE164'; + let logString: string; + switch (mode) { + case SenderCertificateMode.WithE164: + storageKey = 'senderCertificate'; + logString = 'sender certificate WITH E164'; + break; + case SenderCertificateMode.WithoutE164: + storageKey = 'senderCertificateNoE164'; + logString = 'sender certificate WITHOUT E164'; + break; + default: + throw missingCaseError(mode); } runWhenOnline(); + removeOldKey(storage); events.on('timetravel', scheduleNextRotation); function scheduleNextRotation() { const now = Date.now(); - const certificate = storage.get('senderCertificate'); + const certificate = storage.get(storageKey); if (!certificate || !certificate.expires) { setTimeoutForNextRun(scheduledTime || now); @@ -68,16 +89,7 @@ export function initialize({ setTimeoutForNextRun(time); } - // Keeping this entrypoint around so more inialize() calls just kick the timing - scheduleNext = scheduleNextRotation; - - async function saveCert({ - certificate, - key, - }: { - certificate: string; - key: string; - }): Promise { + async function saveCert(certificate: string): Promise { const arrayBuffer = window.Signal.Crypto.base64ToArrayBuffer(certificate); const decodedContainer = window.textsecure.protobuf.SenderCertificate.decode( arrayBuffer @@ -92,19 +104,11 @@ export function initialize({ expires: decodedCert.expires.toNumber(), serialized: arrayBuffer, }; - await storage.put(key, toSave); - } - - async function removeOldKey(): Promise { - const oldCertKey = 'senderCertificateWithUuid'; - const oldUuidCert = storage.get(oldCertKey); - if (oldUuidCert) { - await storage.remove(oldCertKey); - } + await storage.put(storageKey, toSave); } async function run(): Promise { - log.info('refreshSenderCertificate: Getting new certificate...'); + log.info(`refreshSenderCertificate: Getting new ${logString}...`); try { const OLD_USERNAME = storage.get('number_id'); const USERNAME = storage.get('uuid_id'); @@ -114,29 +118,16 @@ export function initialize({ password: PASSWORD, }); - const omitE164 = true; - const [ - { certificate }, - { certificate: certificateWithNoE164 }, - ] = await Promise.all([ - server.getSenderCertificate(), - server.getSenderCertificate(omitE164), - ]); + const omitE164 = mode === SenderCertificateMode.WithoutE164; + const { certificate } = await server.getSenderCertificate(omitE164); - await Promise.all([ - saveCert({ certificate, key: 'senderCertificate' }), - saveCert({ - certificate: certificateWithNoE164, - key: 'senderCertificateNoE164', - }), - removeOldKey(), - ]); + await saveCert(certificate); scheduledTime = null; scheduleNextRotation(); } catch (error) { log.error( - 'refreshSenderCertificate: Get failed. Trying again in five minutes...', + `refreshSenderCertificate: Get failed for ${logString}. Trying again in five minutes...`, error && error.stack ? error.stack : error ); @@ -171,7 +162,7 @@ export function initialize({ if (scheduledTime !== time || !timeout) { log.info( - 'Next sender certificate refresh scheduled for', + `refreshSenderCertificate: Next ${logString} refresh scheduled for`, new Date(time).toISOString() ); }