Periodically refresh both sender certificates

This commit is contained in:
Evan Hahn 2021-04-02 11:23:47 -05:00 committed by Josh Perez
parent 99928ee831
commit b95dd207ca
3 changed files with 53 additions and 50 deletions

View file

@ -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<void> {
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();

View file

@ -39,6 +39,11 @@ type ValidatorType = {
): Promise<void>;
};
export const enum SenderCertificateMode {
WithE164,
WithoutE164,
}
export type SerializedCertificateType = {
serialized: ArrayBuffer;
};

View file

@ -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<typeof setTimeout> = 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<void> {
async function saveCert(certificate: string): Promise<void> {
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<void> {
const oldCertKey = 'senderCertificateWithUuid';
const oldUuidCert = storage.get(oldCertKey);
if (oldUuidCert) {
await storage.remove(oldCertKey);
}
await storage.put(storageKey, toSave);
}
async function run(): Promise<void> {
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()
);
}