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