2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2020 Signal Messenger, LLC
|
2020-10-30 20:34:04 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2020-09-24 21:53:21 +00:00
|
|
|
import PQueue from 'p-queue';
|
2023-07-14 16:53:20 +00:00
|
|
|
import { isNumber, omit, orderBy } from 'lodash';
|
2020-04-13 17:37:29 +00:00
|
|
|
|
|
|
|
import EventTarget from './EventTarget';
|
2023-07-14 16:53:20 +00:00
|
|
|
import type {
|
|
|
|
UploadKeysType,
|
|
|
|
UploadKyberPreKeyType,
|
|
|
|
UploadPreKeyType,
|
|
|
|
UploadSignedPreKeyType,
|
|
|
|
WebAPIType,
|
|
|
|
} from './WebAPI';
|
|
|
|
import type {
|
|
|
|
CompatPreKeyType,
|
|
|
|
KeyPairType,
|
|
|
|
KyberPreKeyType,
|
|
|
|
PniKeyMaterialType,
|
|
|
|
} from './Types.d';
|
2020-04-13 17:37:29 +00:00
|
|
|
import ProvisioningCipher from './ProvisioningCipher';
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { IncomingWebSocketRequest } from './WebsocketResources';
|
2021-07-28 21:37:09 +00:00
|
|
|
import createTaskWithTimeout from './TaskWithTimeout';
|
2021-07-02 19:21:24 +00:00
|
|
|
import * as Bytes from '../Bytes';
|
2021-09-27 17:31:34 +00:00
|
|
|
import { RemoveAllConfiguration } from '../types/RemoveAllConfiguration';
|
2021-12-09 19:45:21 +00:00
|
|
|
import * as Errors from '../types/errors';
|
2021-09-27 17:31:34 +00:00
|
|
|
import { senderCertificateService } from '../services/senderCertificate';
|
2021-04-16 23:13:13 +00:00
|
|
|
import {
|
|
|
|
deriveAccessKey,
|
|
|
|
generateRegistrationId,
|
|
|
|
getRandomBytes,
|
2021-09-24 00:49:05 +00:00
|
|
|
decryptDeviceName,
|
|
|
|
encryptDeviceName,
|
2021-04-16 23:13:13 +00:00
|
|
|
} from '../Crypto';
|
|
|
|
import {
|
|
|
|
generateKeyPair,
|
|
|
|
generateSignedPreKey,
|
|
|
|
generatePreKey,
|
2023-07-14 16:53:20 +00:00
|
|
|
generateKyberPreKey,
|
2021-04-16 23:13:13 +00:00
|
|
|
} from '../Curve';
|
2023-08-10 16:43:33 +00:00
|
|
|
import type { ServiceIdString, PniString } from '../types/ServiceId';
|
2023-08-16 20:54:39 +00:00
|
|
|
import {
|
|
|
|
ServiceIdKind,
|
|
|
|
normalizeAci,
|
|
|
|
toTaggedPni,
|
|
|
|
isUntaggedPniString,
|
|
|
|
} from '../types/ServiceId';
|
2021-03-22 21:08:52 +00:00
|
|
|
import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
|
2021-05-05 16:39:16 +00:00
|
|
|
import { ourProfileKeyService } from '../services/ourProfileKey';
|
2022-09-15 19:17:15 +00:00
|
|
|
import { assertDev, strictAssert } from '../util/assert';
|
2022-06-01 17:48:16 +00:00
|
|
|
import { getRegionCodeForNumber } from '../util/libphonenumberUtil';
|
2021-06-07 16:27:02 +00:00
|
|
|
import { getProvisioningUrl } from '../util/getProvisioningUrl';
|
2022-07-18 22:32:00 +00:00
|
|
|
import { isNotNil } from '../util/isNotNil';
|
2021-07-02 19:21:24 +00:00
|
|
|
import { SignalService as Proto } from '../protobuf';
|
2021-09-17 18:27:53 +00:00
|
|
|
import * as log from '../logging/log';
|
2023-07-14 16:53:20 +00:00
|
|
|
import type { StorageAccessType } from '../types/Storage';
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
type StorageKeyByServiceIdKind = {
|
|
|
|
[kind in ServiceIdKind]: keyof StorageAccessType;
|
2023-07-14 16:53:20 +00:00
|
|
|
};
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-08-03 22:26:00 +00:00
|
|
|
const DAY = 24 * 60 * 60 * 1000;
|
2023-07-14 16:53:20 +00:00
|
|
|
|
|
|
|
const STARTING_KEY_ID = 1;
|
2021-04-16 23:13:13 +00:00
|
|
|
const PROFILE_KEY_LENGTH = 32;
|
2023-07-14 16:53:20 +00:00
|
|
|
const KEY_TOO_OLD_THRESHOLD = 14 * DAY;
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
export const KYBER_KEY_ID_KEY: StorageKeyByServiceIdKind = {
|
|
|
|
[ServiceIdKind.ACI]: 'maxKyberPreKeyId',
|
|
|
|
[ServiceIdKind.Unknown]: 'maxKyberPreKeyId',
|
|
|
|
[ServiceIdKind.PNI]: 'maxKyberPreKeyIdPNI',
|
2023-07-14 16:53:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const LAST_RESORT_KEY_ARCHIVE_AGE = 30 * DAY;
|
|
|
|
const LAST_RESORT_KEY_ROTATION_AGE = DAY * 1.5;
|
|
|
|
const LAST_RESORT_KEY_MINIMUM = 5;
|
2023-08-10 16:43:33 +00:00
|
|
|
const LAST_RESORT_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = {
|
|
|
|
[ServiceIdKind.ACI]: 'lastResortKeyUpdateTime',
|
|
|
|
[ServiceIdKind.Unknown]: 'lastResortKeyUpdateTime',
|
|
|
|
[ServiceIdKind.PNI]: 'lastResortKeyUpdateTimePNI',
|
2023-07-14 16:53:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const PRE_KEY_ARCHIVE_AGE = 90 * DAY;
|
|
|
|
const PRE_KEY_GEN_BATCH_SIZE = 100;
|
|
|
|
const PRE_KEY_MAX_COUNT = 200;
|
2023-08-10 16:43:33 +00:00
|
|
|
const PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = {
|
|
|
|
[ServiceIdKind.ACI]: 'maxPreKeyId',
|
|
|
|
[ServiceIdKind.Unknown]: 'maxPreKeyId',
|
|
|
|
[ServiceIdKind.PNI]: 'maxPreKeyIdPNI',
|
2023-07-14 16:53:20 +00:00
|
|
|
};
|
|
|
|
const PRE_KEY_MINIMUM = 10;
|
|
|
|
|
|
|
|
const SIGNED_PRE_KEY_ARCHIVE_AGE = 30 * DAY;
|
2023-08-10 16:43:33 +00:00
|
|
|
export const SIGNED_PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = {
|
|
|
|
[ServiceIdKind.ACI]: 'signedKeyId',
|
|
|
|
[ServiceIdKind.Unknown]: 'signedKeyId',
|
|
|
|
[ServiceIdKind.PNI]: 'signedKeyIdPNI',
|
2023-07-14 16:53:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const SIGNED_PRE_KEY_ROTATION_AGE = DAY * 1.5;
|
|
|
|
const SIGNED_PRE_KEY_MINIMUM = 5;
|
2023-08-10 16:43:33 +00:00
|
|
|
const SIGNED_PRE_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = {
|
|
|
|
[ServiceIdKind.ACI]: 'signedKeyUpdateTime',
|
|
|
|
[ServiceIdKind.Unknown]: 'signedKeyUpdateTime',
|
|
|
|
[ServiceIdKind.PNI]: 'signedKeyUpdateTimePNI',
|
2020-04-13 17:37:29 +00:00
|
|
|
};
|
|
|
|
|
2021-12-03 02:06:32 +00:00
|
|
|
type CreateAccountOptionsType = Readonly<{
|
|
|
|
number: string;
|
|
|
|
verificationCode: string;
|
2022-03-01 23:01:21 +00:00
|
|
|
aciKeyPair: KeyPairType;
|
2021-12-03 02:06:32 +00:00
|
|
|
pniKeyPair?: KeyPairType;
|
|
|
|
profileKey?: Uint8Array;
|
|
|
|
deviceName?: string;
|
|
|
|
userAgent?: string;
|
|
|
|
readReceipts?: boolean;
|
|
|
|
accessKey?: Uint8Array;
|
|
|
|
}>;
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
function getNextKeyId(
|
|
|
|
kind: ServiceIdKind,
|
|
|
|
keys: StorageKeyByServiceIdKind
|
|
|
|
): number {
|
2023-07-14 16:53:20 +00:00
|
|
|
const id = window.storage.get(keys[kind]);
|
|
|
|
|
|
|
|
if (isNumber(id)) {
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For PNI ids, start with existing ACI id
|
2023-08-10 16:43:33 +00:00
|
|
|
if (kind === ServiceIdKind.PNI) {
|
|
|
|
return window.storage.get(keys[ServiceIdKind.ACI], STARTING_KEY_ID);
|
2023-07-14 16:53:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return STARTING_KEY_ID;
|
|
|
|
}
|
|
|
|
|
2020-04-13 17:37:29 +00:00
|
|
|
export default class AccountManager extends EventTarget {
|
|
|
|
pending: Promise<void>;
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2020-04-13 17:37:29 +00:00
|
|
|
pendingQueue?: PQueue;
|
|
|
|
|
2021-07-23 17:23:50 +00:00
|
|
|
constructor(private readonly server: WebAPIType) {
|
2020-04-13 17:37:29 +00:00
|
|
|
super();
|
|
|
|
|
|
|
|
this.pending = Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
private async queueTask<T>(task: () => Promise<T>): Promise<T> {
|
|
|
|
this.pendingQueue = this.pendingQueue || new PQueue({ concurrency: 1 });
|
|
|
|
const taskWithTimeout = createTaskWithTimeout(task, 'AccountManager task');
|
|
|
|
|
|
|
|
return this.pendingQueue.add(taskWithTimeout);
|
|
|
|
}
|
|
|
|
|
2022-03-25 17:36:08 +00:00
|
|
|
async requestVoiceVerification(number: string, token: string): Promise<void> {
|
2021-11-30 17:51:53 +00:00
|
|
|
return this.server.requestVerificationVoice(number, token);
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2022-03-25 17:36:08 +00:00
|
|
|
async requestSMSVerification(number: string, token: string): Promise<void> {
|
2021-11-30 17:51:53 +00:00
|
|
|
return this.server.requestVerificationSMS(number, token);
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2022-03-25 17:36:08 +00:00
|
|
|
encryptDeviceName(name: string, identityKey: KeyPairType): string | null {
|
2020-04-13 17:37:29 +00:00
|
|
|
if (!name) {
|
|
|
|
return null;
|
|
|
|
}
|
2021-09-24 00:49:05 +00:00
|
|
|
const encrypted = encryptDeviceName(name, identityKey.pubKey);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-07-02 19:21:24 +00:00
|
|
|
const proto = new Proto.DeviceName();
|
2021-09-24 00:49:05 +00:00
|
|
|
proto.ephemeralPublic = encrypted.ephemeralPublic;
|
|
|
|
proto.syntheticIv = encrypted.syntheticIv;
|
|
|
|
proto.ciphertext = encrypted.ciphertext;
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-07-02 19:21:24 +00:00
|
|
|
const bytes = Proto.DeviceName.encode(proto).finish();
|
|
|
|
return Bytes.toBase64(bytes);
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2022-03-25 17:36:08 +00:00
|
|
|
async decryptDeviceName(base64: string): Promise<string> {
|
2023-08-10 16:43:33 +00:00
|
|
|
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
2021-11-11 22:43:05 +00:00
|
|
|
const identityKey =
|
2023-08-10 16:43:33 +00:00
|
|
|
window.textsecure.storage.protocol.getIdentityKeyPair(ourAci);
|
2021-04-16 23:13:13 +00:00
|
|
|
if (!identityKey) {
|
|
|
|
throw new Error('decryptDeviceName: No identity key pair!');
|
|
|
|
}
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-07-02 19:21:24 +00:00
|
|
|
const bytes = Bytes.fromBase64(base64);
|
|
|
|
const proto = Proto.DeviceName.decode(bytes);
|
2022-09-15 19:17:15 +00:00
|
|
|
assertDev(
|
2021-07-02 19:21:24 +00:00
|
|
|
proto.ephemeralPublic && proto.syntheticIv && proto.ciphertext,
|
|
|
|
'Missing required fields in DeviceName'
|
|
|
|
);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-09-24 00:49:05 +00:00
|
|
|
const name = decryptDeviceName(proto, identityKey.privKey);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2022-03-25 17:36:08 +00:00
|
|
|
async maybeUpdateDeviceName(): Promise<void> {
|
2021-11-11 22:43:05 +00:00
|
|
|
const isNameEncrypted =
|
|
|
|
window.textsecure.storage.user.getDeviceNameEncrypted();
|
2020-04-13 17:37:29 +00:00
|
|
|
if (isNameEncrypted) {
|
|
|
|
return;
|
|
|
|
}
|
2021-09-10 02:38:11 +00:00
|
|
|
const { storage } = window.textsecure;
|
|
|
|
const deviceName = storage.user.getDeviceName();
|
2022-08-15 21:53:33 +00:00
|
|
|
const identityKeyPair = storage.protocol.getIdentityKeyPair(
|
2023-08-10 16:43:33 +00:00
|
|
|
storage.user.getCheckedAci()
|
2021-09-10 02:38:11 +00:00
|
|
|
);
|
|
|
|
strictAssert(
|
|
|
|
identityKeyPair !== undefined,
|
|
|
|
"Can't encrypt device name without identity key pair"
|
|
|
|
);
|
2021-09-24 00:49:05 +00:00
|
|
|
const base64 = this.encryptDeviceName(deviceName || '', identityKeyPair);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
|
|
|
if (base64) {
|
|
|
|
await this.server.updateDeviceName(base64);
|
|
|
|
}
|
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2022-03-25 17:36:08 +00:00
|
|
|
async deviceNameIsEncrypted(): Promise<void> {
|
2020-04-13 17:37:29 +00:00
|
|
|
await window.textsecure.storage.user.setDeviceNameEncrypted();
|
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2022-03-25 17:36:08 +00:00
|
|
|
async registerSingleDevice(
|
|
|
|
number: string,
|
|
|
|
verificationCode: string
|
|
|
|
): Promise<void> {
|
2023-07-14 16:53:20 +00:00
|
|
|
await this.queueTask(async () => {
|
2022-03-01 23:01:21 +00:00
|
|
|
const aciKeyPair = generateKeyPair();
|
2021-12-03 02:06:32 +00:00
|
|
|
const pniKeyPair = generateKeyPair();
|
2021-04-16 23:13:13 +00:00
|
|
|
const profileKey = getRandomBytes(PROFILE_KEY_LENGTH);
|
2021-09-24 00:49:05 +00:00
|
|
|
const accessKey = deriveAccessKey(profileKey);
|
2021-04-16 23:13:13 +00:00
|
|
|
|
2021-12-17 21:26:50 +00:00
|
|
|
const registrationBaton = this.server.startRegistration();
|
|
|
|
try {
|
|
|
|
await this.createAccount({
|
|
|
|
number,
|
|
|
|
verificationCode,
|
2022-03-01 23:01:21 +00:00
|
|
|
aciKeyPair,
|
2021-12-17 21:26:50 +00:00
|
|
|
pniKeyPair,
|
|
|
|
profileKey,
|
|
|
|
accessKey,
|
|
|
|
});
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const uploadKeys = async (kind: ServiceIdKind) => {
|
2023-07-14 16:53:20 +00:00
|
|
|
const keys = await this._generateKeys(PRE_KEY_GEN_BATCH_SIZE, kind);
|
|
|
|
await this.server.registerKeys(keys, kind);
|
|
|
|
await this._confirmKeys(keys, kind);
|
|
|
|
};
|
2021-12-17 21:26:50 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
await uploadKeys(ServiceIdKind.ACI);
|
|
|
|
await uploadKeys(ServiceIdKind.PNI);
|
2021-12-17 21:26:50 +00:00
|
|
|
} finally {
|
|
|
|
this.server.finishRegistration(registrationBaton);
|
|
|
|
}
|
2021-04-16 23:13:13 +00:00
|
|
|
await this.registrationDone();
|
|
|
|
});
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async registerSecondDevice(
|
2022-03-25 17:36:08 +00:00
|
|
|
setProvisioningUrl: (url: string) => void,
|
2021-12-03 17:46:44 +00:00
|
|
|
confirmNumber: (number?: string) => Promise<string>
|
2022-03-25 17:36:08 +00:00
|
|
|
): Promise<void> {
|
2020-04-13 17:37:29 +00:00
|
|
|
const provisioningCipher = new ProvisioningCipher();
|
2021-06-09 22:28:54 +00:00
|
|
|
const pubKey = await provisioningCipher.getPublicKey();
|
|
|
|
|
2021-07-28 21:37:09 +00:00
|
|
|
let envelopeCallbacks:
|
|
|
|
| {
|
|
|
|
resolve(data: Proto.ProvisionEnvelope): void;
|
|
|
|
reject(error: Error): void;
|
|
|
|
}
|
|
|
|
| undefined;
|
|
|
|
const envelopePromise = new Promise<Proto.ProvisionEnvelope>(
|
|
|
|
(resolve, reject) => {
|
|
|
|
envelopeCallbacks = { resolve, reject };
|
|
|
|
}
|
|
|
|
);
|
2021-06-09 22:28:54 +00:00
|
|
|
|
2021-07-28 21:37:09 +00:00
|
|
|
const wsr = await this.server.getProvisioningResource({
|
|
|
|
handleRequest(request: IncomingWebSocketRequest) {
|
|
|
|
if (
|
|
|
|
request.path === '/v1/address' &&
|
|
|
|
request.verb === 'PUT' &&
|
|
|
|
request.body
|
|
|
|
) {
|
|
|
|
const proto = Proto.ProvisioningUuid.decode(request.body);
|
|
|
|
const { uuid } = proto;
|
|
|
|
if (!uuid) {
|
|
|
|
throw new Error('registerSecondDevice: expected a UUID');
|
|
|
|
}
|
|
|
|
const url = getProvisioningUrl(uuid, pubKey);
|
2021-06-09 22:28:54 +00:00
|
|
|
|
2023-01-19 00:02:03 +00:00
|
|
|
window.SignalCI?.setProvisioningURL(url);
|
2021-07-28 21:37:09 +00:00
|
|
|
|
|
|
|
setProvisioningUrl(url);
|
|
|
|
request.respond(200, 'OK');
|
|
|
|
} else if (
|
|
|
|
request.path === '/v1/message' &&
|
|
|
|
request.verb === 'PUT' &&
|
|
|
|
request.body
|
|
|
|
) {
|
|
|
|
const envelope = Proto.ProvisionEnvelope.decode(request.body);
|
|
|
|
request.respond(200, 'OK');
|
|
|
|
wsr.close();
|
|
|
|
envelopeCallbacks?.resolve(envelope);
|
|
|
|
} else {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.error('Unknown websocket message', request.path);
|
2021-06-09 22:28:54 +00:00
|
|
|
}
|
2021-07-28 21:37:09 +00:00
|
|
|
},
|
|
|
|
});
|
2021-06-09 22:28:54 +00:00
|
|
|
|
2021-09-17 18:27:53 +00:00
|
|
|
log.info('provisioning socket open');
|
2021-06-09 22:28:54 +00:00
|
|
|
|
2021-07-28 21:37:09 +00:00
|
|
|
wsr.addEventListener('close', ({ code, reason }) => {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.info(`provisioning socket closed. Code: ${code} Reason: ${reason}`);
|
2021-06-09 22:28:54 +00:00
|
|
|
|
2021-07-28 21:37:09 +00:00
|
|
|
// Note: if we have resolved the envelope already - this has no effect
|
|
|
|
envelopeCallbacks?.reject(new Error('websocket closed'));
|
|
|
|
});
|
|
|
|
|
|
|
|
const envelope = await envelopePromise;
|
|
|
|
const provisionMessage = await provisioningCipher.decrypt(envelope);
|
|
|
|
|
|
|
|
await this.queueTask(async () => {
|
|
|
|
const deviceName = await confirmNumber(provisionMessage.number);
|
|
|
|
if (typeof deviceName !== 'string' || deviceName.length === 0) {
|
|
|
|
throw new Error(
|
|
|
|
'AccountManager.registerSecondDevice: Invalid device name'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
!provisionMessage.number ||
|
|
|
|
!provisionMessage.provisioningCode ||
|
2022-03-01 23:01:21 +00:00
|
|
|
!provisionMessage.aciKeyPair
|
2021-07-28 21:37:09 +00:00
|
|
|
) {
|
|
|
|
throw new Error(
|
|
|
|
'AccountManager.registerSecondDevice: Provision message was missing key data'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-17 21:26:50 +00:00
|
|
|
const registrationBaton = this.server.startRegistration();
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.createAccount({
|
|
|
|
number: provisionMessage.number,
|
|
|
|
verificationCode: provisionMessage.provisioningCode,
|
2022-03-01 23:01:21 +00:00
|
|
|
aciKeyPair: provisionMessage.aciKeyPair,
|
|
|
|
pniKeyPair: provisionMessage.pniKeyPair,
|
2021-12-17 21:26:50 +00:00
|
|
|
profileKey: provisionMessage.profileKey,
|
|
|
|
deviceName,
|
|
|
|
userAgent: provisionMessage.userAgent,
|
|
|
|
readReceipts: provisionMessage.readReceipts,
|
|
|
|
});
|
2022-03-01 23:01:21 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const uploadKeys = async (kind: ServiceIdKind) => {
|
2023-07-14 16:53:20 +00:00
|
|
|
const keys = await this._generateKeys(PRE_KEY_GEN_BATCH_SIZE, kind);
|
2022-03-01 23:01:21 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
try {
|
|
|
|
await this.server.registerKeys(keys, kind);
|
|
|
|
await this._confirmKeys(keys, kind);
|
|
|
|
} catch (error) {
|
2023-08-10 16:43:33 +00:00
|
|
|
if (kind === ServiceIdKind.PNI) {
|
2023-07-14 16:53:20 +00:00
|
|
|
log.error(
|
|
|
|
'Failed to upload PNI prekeys. Moving on',
|
|
|
|
Errors.toLogFormat(error)
|
|
|
|
);
|
|
|
|
return;
|
2022-04-22 19:02:23 +00:00
|
|
|
}
|
2023-07-14 16:53:20 +00:00
|
|
|
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
await uploadKeys(ServiceIdKind.ACI);
|
2023-07-14 16:53:20 +00:00
|
|
|
if (provisionMessage.pniKeyPair) {
|
2023-08-10 16:43:33 +00:00
|
|
|
await uploadKeys(ServiceIdKind.PNI);
|
2023-07-14 16:53:20 +00:00
|
|
|
}
|
2021-12-17 21:26:50 +00:00
|
|
|
} finally {
|
|
|
|
this.server.finishRegistration(registrationBaton);
|
|
|
|
}
|
|
|
|
|
2021-07-28 21:37:09 +00:00
|
|
|
await this.registrationDone();
|
2021-06-09 22:28:54 +00:00
|
|
|
});
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
private getIdentityKeyOrThrow(ourServiceId: ServiceIdString): KeyPairType {
|
2023-07-14 16:53:20 +00:00
|
|
|
const { storage } = window.textsecure;
|
|
|
|
const store = storage.protocol;
|
|
|
|
let identityKey: KeyPairType | undefined;
|
|
|
|
try {
|
2023-08-10 16:43:33 +00:00
|
|
|
identityKey = store.getIdentityKeyPair(ourServiceId);
|
2023-07-14 16:53:20 +00:00
|
|
|
} catch (error) {
|
|
|
|
const errorText = Errors.toLogFormat(error);
|
|
|
|
throw new Error(
|
2023-07-14 19:18:32 +00:00
|
|
|
`getIdentityKeyOrThrow: Failed to fetch identity key - ${errorText}`
|
2023-06-09 01:08:24 +00:00
|
|
|
);
|
2023-07-14 16:53:20 +00:00
|
|
|
}
|
2023-06-09 01:08:24 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
if (!identityKey) {
|
2023-07-14 19:18:32 +00:00
|
|
|
throw new Error('getIdentityKeyOrThrow: Missing identity key');
|
2023-07-14 16:53:20 +00:00
|
|
|
}
|
2023-06-09 01:08:24 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
return identityKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async generateNewPreKeys(
|
2023-08-10 16:43:33 +00:00
|
|
|
serviceIdKind: ServiceIdKind,
|
2023-07-14 16:53:20 +00:00
|
|
|
count: number
|
|
|
|
): Promise<Array<UploadPreKeyType>> {
|
2023-08-10 16:43:33 +00:00
|
|
|
const logId = `AccountManager.generateNewPreKeys(${serviceIdKind})`;
|
2023-07-14 16:53:20 +00:00
|
|
|
const { storage } = window.textsecure;
|
|
|
|
const store = storage.protocol;
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const startId = getNextKeyId(serviceIdKind, PRE_KEY_ID_KEY);
|
2023-07-14 16:53:20 +00:00
|
|
|
log.info(`${logId}: Generating ${count} new keys starting at ${startId}`);
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
|
2023-07-14 16:53:20 +00:00
|
|
|
if (typeof startId !== 'number') {
|
|
|
|
throw new Error(
|
2023-08-10 16:43:33 +00:00
|
|
|
`${logId}: Invalid ${PRE_KEY_ID_KEY[serviceIdKind]} in storage`
|
2023-06-09 01:08:24 +00:00
|
|
|
);
|
2023-07-14 16:53:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const toSave: Array<CompatPreKeyType> = [];
|
|
|
|
for (let keyId = startId; keyId < startId + count; keyId += 1) {
|
|
|
|
toSave.push(generatePreKey(keyId));
|
|
|
|
}
|
|
|
|
|
2023-07-14 19:18:32 +00:00
|
|
|
await Promise.all([
|
2023-08-10 16:43:33 +00:00
|
|
|
store.storePreKeys(ourServiceId, toSave),
|
|
|
|
storage.put(PRE_KEY_ID_KEY[serviceIdKind], startId + count),
|
2023-07-14 19:18:32 +00:00
|
|
|
]);
|
2023-07-14 16:53:20 +00:00
|
|
|
|
|
|
|
return toSave.map(key => ({
|
|
|
|
keyId: key.keyId,
|
|
|
|
publicKey: key.keyPair.pubKey,
|
|
|
|
}));
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
private async generateNewKyberPreKeys(
|
2023-08-10 16:43:33 +00:00
|
|
|
serviceIdKind: ServiceIdKind,
|
2023-07-14 16:53:20 +00:00
|
|
|
count: number
|
|
|
|
): Promise<Array<UploadKyberPreKeyType>> {
|
2023-08-10 16:43:33 +00:00
|
|
|
const logId = `AccountManager.generateNewKyberPreKeys(${serviceIdKind})`;
|
2023-07-14 16:53:20 +00:00
|
|
|
const { storage } = window.textsecure;
|
|
|
|
const store = storage.protocol;
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const startId = getNextKeyId(serviceIdKind, KYBER_KEY_ID_KEY);
|
2023-07-14 16:53:20 +00:00
|
|
|
log.info(`${logId}: Generating ${count} new keys starting at ${startId}`);
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
|
2023-07-14 16:53:20 +00:00
|
|
|
if (typeof startId !== 'number') {
|
|
|
|
throw new Error(
|
2023-08-10 16:43:33 +00:00
|
|
|
`${logId}: Invalid ${KYBER_KEY_ID_KEY[serviceIdKind]} in storage`
|
2023-07-14 16:53:20 +00:00
|
|
|
);
|
|
|
|
}
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
const toSave: Array<Omit<KyberPreKeyType, 'id'>> = [];
|
|
|
|
const toUpload: Array<UploadKyberPreKeyType> = [];
|
|
|
|
const now = Date.now();
|
|
|
|
for (let keyId = startId; keyId < startId + count; keyId += 1) {
|
|
|
|
const record = generateKyberPreKey(identityKey, keyId);
|
|
|
|
toSave.push({
|
|
|
|
createdAt: now,
|
|
|
|
data: record.serialize(),
|
|
|
|
isConfirmed: false,
|
|
|
|
isLastResort: false,
|
|
|
|
keyId,
|
2023-08-16 20:54:39 +00:00
|
|
|
ourServiceId,
|
2023-07-14 16:53:20 +00:00
|
|
|
});
|
|
|
|
toUpload.push({
|
|
|
|
keyId,
|
|
|
|
publicKey: record.publicKey().serialize(),
|
|
|
|
signature: record.signature(),
|
|
|
|
});
|
|
|
|
}
|
2020-06-11 20:29:14 +00:00
|
|
|
|
2023-07-14 19:18:32 +00:00
|
|
|
await Promise.all([
|
2023-08-10 16:43:33 +00:00
|
|
|
store.storeKyberPreKeys(ourServiceId, toSave),
|
|
|
|
storage.put(KYBER_KEY_ID_KEY[serviceIdKind], startId + count),
|
2023-07-14 19:18:32 +00:00
|
|
|
]);
|
2023-07-14 16:53:20 +00:00
|
|
|
|
|
|
|
return toUpload;
|
|
|
|
}
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
async maybeUpdateKeys(serviceIdKind: ServiceIdKind): Promise<void> {
|
|
|
|
const logId = `maybeUpdateKeys(${serviceIdKind})`;
|
2023-07-14 16:53:20 +00:00
|
|
|
await this.queueTask(async () => {
|
2023-08-21 20:15:10 +00:00
|
|
|
const { storage } = window.textsecure;
|
|
|
|
let identityKey: KeyPairType;
|
|
|
|
|
|
|
|
try {
|
|
|
|
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
|
|
|
|
identityKey = this.getIdentityKeyOrThrow(ourServiceId);
|
|
|
|
} catch (error) {
|
|
|
|
if (serviceIdKind === ServiceIdKind.PNI) {
|
|
|
|
log.info(
|
|
|
|
`${logId}: Not enough information to update PNI keys`,
|
|
|
|
Errors.toLogFormat(error)
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
const { count: preKeyCount, pqCount: kyberPreKeyCount } =
|
2023-08-10 16:43:33 +00:00
|
|
|
await this.server.getMyKeyCounts(serviceIdKind);
|
2023-07-14 16:53:20 +00:00
|
|
|
|
|
|
|
let preKeys: Array<UploadPreKeyType> | undefined;
|
|
|
|
if (preKeyCount < PRE_KEY_MINIMUM) {
|
|
|
|
log.info(
|
|
|
|
`${logId}: Server prekey count is ${preKeyCount}, generating a new set`
|
|
|
|
);
|
|
|
|
preKeys = await this.generateNewPreKeys(
|
2023-08-10 16:43:33 +00:00
|
|
|
serviceIdKind,
|
2023-07-14 16:53:20 +00:00
|
|
|
PRE_KEY_GEN_BATCH_SIZE
|
2020-06-11 20:29:14 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
let pqPreKeys: Array<UploadKyberPreKeyType> | undefined;
|
|
|
|
if (kyberPreKeyCount < PRE_KEY_MINIMUM) {
|
|
|
|
log.info(
|
|
|
|
`${logId}: Server kyber prekey count is ${kyberPreKeyCount}, generating a new set`
|
|
|
|
);
|
|
|
|
pqPreKeys = await this.generateNewKyberPreKeys(
|
2023-08-10 16:43:33 +00:00
|
|
|
serviceIdKind,
|
2023-07-14 16:53:20 +00:00
|
|
|
PRE_KEY_GEN_BATCH_SIZE
|
2021-12-09 19:45:21 +00:00
|
|
|
);
|
2023-07-14 16:53:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const pqLastResortPreKey = await this.maybeUpdateLastResortKyberKey(
|
2023-08-10 16:43:33 +00:00
|
|
|
serviceIdKind
|
2023-07-14 16:53:20 +00:00
|
|
|
);
|
2023-08-10 16:43:33 +00:00
|
|
|
const signedPreKey = await this.maybeUpdateSignedPreKey(serviceIdKind);
|
2023-07-14 16:53:20 +00:00
|
|
|
|
|
|
|
if (
|
|
|
|
!preKeys?.length &&
|
|
|
|
!signedPreKey &&
|
|
|
|
!pqLastResortPreKey &&
|
|
|
|
!pqPreKeys?.length
|
|
|
|
) {
|
|
|
|
log.info(`${logId}: No new keys are needed; returning early`);
|
2021-12-09 19:45:21 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-04-16 23:13:13 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
const keySummary: Array<string> = [];
|
|
|
|
if (preKeys?.length) {
|
|
|
|
keySummary.push(`${!preKeys?.length || 0} prekeys`);
|
|
|
|
}
|
|
|
|
if (signedPreKey) {
|
|
|
|
keySummary.push('a signed prekey');
|
|
|
|
}
|
|
|
|
if (pqLastResortPreKey) {
|
|
|
|
keySummary.push('a last-resort kyber prekey');
|
|
|
|
}
|
|
|
|
if (pqPreKeys?.length) {
|
|
|
|
keySummary.push(`${!pqPreKeys?.length || 0} kyber prekeys`);
|
2021-12-09 19:45:21 +00:00
|
|
|
}
|
2023-07-14 16:53:20 +00:00
|
|
|
log.info(`${logId}: Uploading with ${keySummary.join(', ')}`);
|
|
|
|
|
|
|
|
const toUpload = {
|
|
|
|
identityKey: identityKey.pubKey,
|
|
|
|
preKeys,
|
|
|
|
pqPreKeys,
|
|
|
|
pqLastResortPreKey,
|
|
|
|
signedPreKey,
|
|
|
|
};
|
2021-12-09 19:45:21 +00:00
|
|
|
|
2023-08-29 00:26:39 +00:00
|
|
|
await this.server.registerKeys(toUpload, serviceIdKind);
|
2023-08-10 16:43:33 +00:00
|
|
|
await this._confirmKeys(toUpload, serviceIdKind);
|
2023-07-14 16:53:20 +00:00
|
|
|
|
|
|
|
const { count: updatedPreKeyCount, pqCount: updatedKyberPreKeyCount } =
|
2023-08-10 16:43:33 +00:00
|
|
|
await this.server.getMyKeyCounts(serviceIdKind);
|
2021-12-09 19:45:21 +00:00
|
|
|
log.info(
|
2023-07-14 16:53:20 +00:00
|
|
|
`${logId}: Successfully updated; ` +
|
|
|
|
`server prekey count: ${updatedPreKeyCount}, ` +
|
|
|
|
`server kyber prekey count: ${updatedKyberPreKeyCount}`
|
2021-12-09 19:45:21 +00:00
|
|
|
);
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
await this._cleanSignedPreKeys(serviceIdKind);
|
|
|
|
await this._cleanLastResortKeys(serviceIdKind);
|
|
|
|
await this._cleanPreKeys(serviceIdKind);
|
|
|
|
await this._cleanKyberPreKeys(serviceIdKind);
|
2023-07-14 16:53:20 +00:00
|
|
|
});
|
|
|
|
}
|
2021-12-09 19:45:21 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
areKeysOutOfDate(serviceIdKind: ServiceIdKind): boolean {
|
2023-07-14 16:53:20 +00:00
|
|
|
const signedPreKeyTime = window.storage.get(
|
2023-08-10 16:43:33 +00:00
|
|
|
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
2023-07-14 16:53:20 +00:00
|
|
|
0
|
|
|
|
);
|
|
|
|
const lastResortKeyTime = window.storage.get(
|
2023-08-10 16:43:33 +00:00
|
|
|
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
2023-07-14 16:53:20 +00:00
|
|
|
0
|
|
|
|
);
|
2021-12-09 19:45:21 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
if (isOlderThan(signedPreKeyTime, KEY_TOO_OLD_THRESHOLD)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (isOlderThan(lastResortKeyTime, KEY_TOO_OLD_THRESHOLD)) {
|
|
|
|
return true;
|
|
|
|
}
|
2021-12-09 19:45:21 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
return false;
|
|
|
|
}
|
2021-12-09 19:45:21 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
private async maybeUpdateSignedPreKey(
|
2023-08-10 16:43:33 +00:00
|
|
|
serviceIdKind: ServiceIdKind
|
2023-07-14 16:53:20 +00:00
|
|
|
): Promise<UploadSignedPreKeyType | undefined> {
|
2023-08-10 16:43:33 +00:00
|
|
|
const logId = `AccountManager.maybeUpdateSignedPreKey(${serviceIdKind})`;
|
2023-07-14 16:53:20 +00:00
|
|
|
const store = window.textsecure.storage.protocol;
|
2021-12-09 19:45:21 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const ourServiceId =
|
|
|
|
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
|
|
|
const signedKeyId = getNextKeyId(serviceIdKind, SIGNED_PRE_KEY_ID_KEY);
|
2023-07-14 16:53:20 +00:00
|
|
|
if (typeof signedKeyId !== 'number') {
|
|
|
|
throw new Error(
|
2023-08-10 16:43:33 +00:00
|
|
|
`${logId}: Invalid ${SIGNED_PRE_KEY_ID_KEY[serviceIdKind]} in storage`
|
2023-07-14 16:53:20 +00:00
|
|
|
);
|
|
|
|
}
|
2021-12-09 19:45:21 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const keys = await store.loadSignedPreKeys(ourServiceId);
|
2023-07-14 16:53:20 +00:00
|
|
|
const sortedKeys = orderBy(keys, ['created_at'], ['desc']);
|
|
|
|
const confirmedKeys = sortedKeys.filter(key => key.confirmed);
|
|
|
|
const mostRecent = confirmedKeys[0];
|
|
|
|
|
|
|
|
const lastUpdate = mostRecent?.created_at;
|
|
|
|
if (isMoreRecentThan(lastUpdate || 0, SIGNED_PRE_KEY_ROTATION_AGE)) {
|
|
|
|
log.warn(
|
|
|
|
`${logId}: ${confirmedKeys.length} confirmed keys, ` +
|
|
|
|
`most recent was created ${lastUpdate}. No need to update.`
|
|
|
|
);
|
|
|
|
const existing = window.storage.get(
|
2023-08-10 16:43:33 +00:00
|
|
|
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind]
|
2023-07-14 16:53:20 +00:00
|
|
|
);
|
|
|
|
if (lastUpdate && !existing) {
|
|
|
|
log.warn(`${logId}: Updating last update time to ${lastUpdate}`);
|
|
|
|
await window.storage.put(
|
2023-08-10 16:43:33 +00:00
|
|
|
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
2023-07-14 16:53:20 +00:00
|
|
|
lastUpdate
|
|
|
|
);
|
2021-12-09 19:45:21 +00:00
|
|
|
}
|
2023-07-14 16:53:20 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
|
2023-07-14 16:53:20 +00:00
|
|
|
|
|
|
|
const key = await generateSignedPreKey(identityKey, signedKeyId);
|
|
|
|
log.info(`${logId}: Saving new signed prekey`, key.keyId);
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
window.textsecure.storage.put(
|
2023-08-10 16:43:33 +00:00
|
|
|
SIGNED_PRE_KEY_ID_KEY[serviceIdKind],
|
2023-07-14 16:53:20 +00:00
|
|
|
signedKeyId + 1
|
|
|
|
),
|
2023-08-10 16:43:33 +00:00
|
|
|
store.storeSignedPreKey(ourServiceId, key.keyId, key.keyPair),
|
2023-07-14 16:53:20 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
return {
|
|
|
|
keyId: key.keyId,
|
|
|
|
publicKey: key.keyPair.pubKey,
|
|
|
|
signature: key.signature,
|
|
|
|
};
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
private async maybeUpdateLastResortKyberKey(
|
2023-08-10 16:43:33 +00:00
|
|
|
serviceIdKind: ServiceIdKind
|
2023-07-14 16:53:20 +00:00
|
|
|
): Promise<UploadSignedPreKeyType | undefined> {
|
2023-08-10 16:43:33 +00:00
|
|
|
const logId = `maybeUpdateLastResortKyberKey(${serviceIdKind})`;
|
2023-07-14 16:53:20 +00:00
|
|
|
const store = window.textsecure.storage.protocol;
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const ourServiceId =
|
|
|
|
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
|
|
|
const kyberKeyId = getNextKeyId(serviceIdKind, KYBER_KEY_ID_KEY);
|
2023-07-14 16:53:20 +00:00
|
|
|
if (typeof kyberKeyId !== 'number') {
|
|
|
|
throw new Error(
|
2023-08-10 16:43:33 +00:00
|
|
|
`${logId}: Invalid ${KYBER_KEY_ID_KEY[serviceIdKind]} in storage`
|
2023-07-14 16:53:20 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const keys = store.loadKyberPreKeys(ourServiceId, { isLastResort: true });
|
2023-07-14 16:53:20 +00:00
|
|
|
const sortedKeys = orderBy(keys, ['createdAt'], ['desc']);
|
|
|
|
const confirmedKeys = sortedKeys.filter(key => key.isConfirmed);
|
|
|
|
const mostRecent = confirmedKeys[0];
|
|
|
|
|
|
|
|
const lastUpdate = mostRecent?.createdAt;
|
|
|
|
if (isMoreRecentThan(lastUpdate || 0, LAST_RESORT_KEY_ROTATION_AGE)) {
|
|
|
|
log.warn(
|
|
|
|
`${logId}: ${confirmedKeys.length} confirmed keys, ` +
|
|
|
|
`most recent was created ${lastUpdate}. No need to update.`
|
|
|
|
);
|
|
|
|
const existing = window.storage.get(
|
2023-08-10 16:43:33 +00:00
|
|
|
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind]
|
2023-07-14 16:53:20 +00:00
|
|
|
);
|
|
|
|
if (lastUpdate && !existing) {
|
|
|
|
log.warn(`${logId}: Updating last update time to ${lastUpdate}`);
|
|
|
|
await window.storage.put(
|
2023-08-10 16:43:33 +00:00
|
|
|
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
2023-07-14 16:53:20 +00:00
|
|
|
lastUpdate
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
|
2023-07-14 16:53:20 +00:00
|
|
|
|
|
|
|
const keyId = kyberKeyId;
|
|
|
|
const record = await generateKyberPreKey(identityKey, keyId);
|
|
|
|
log.info(`${logId}: Saving new last resort prekey`, keyId);
|
|
|
|
const key = {
|
|
|
|
createdAt: Date.now(),
|
|
|
|
data: record.serialize(),
|
|
|
|
isConfirmed: false,
|
|
|
|
isLastResort: true,
|
|
|
|
keyId,
|
2023-08-16 20:54:39 +00:00
|
|
|
ourServiceId,
|
2023-07-14 16:53:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
await Promise.all([
|
2023-08-10 16:43:33 +00:00
|
|
|
window.textsecure.storage.put(
|
|
|
|
KYBER_KEY_ID_KEY[serviceIdKind],
|
|
|
|
kyberKeyId + 1
|
|
|
|
),
|
|
|
|
store.storeKyberPreKeys(ourServiceId, [key]),
|
2023-07-14 16:53:20 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
return {
|
|
|
|
keyId,
|
|
|
|
publicKey: record.publicKey().serialize(),
|
|
|
|
signature: record.signature(),
|
|
|
|
};
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
// Exposed only for tests
|
2023-08-10 16:43:33 +00:00
|
|
|
async _cleanSignedPreKeys(serviceIdKind: ServiceIdKind): Promise<void> {
|
|
|
|
const ourServiceId =
|
|
|
|
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
2020-04-13 17:37:29 +00:00
|
|
|
const store = window.textsecure.storage.protocol;
|
2023-08-10 16:43:33 +00:00
|
|
|
const logId = `AccountManager.cleanSignedPreKeys(${serviceIdKind})`;
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const allKeys = store.loadSignedPreKeys(ourServiceId);
|
2023-07-14 16:53:20 +00:00
|
|
|
const sortedKeys = orderBy(allKeys, ['created_at'], ['desc']);
|
|
|
|
const confirmed = sortedKeys.filter(key => key.confirmed);
|
|
|
|
const unconfirmed = sortedKeys.filter(key => !key.confirmed);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
const recent = sortedKeys[0] ? sortedKeys[0].keyId : 'none';
|
2021-08-03 22:26:00 +00:00
|
|
|
const recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none';
|
|
|
|
const recentUnconfirmed = unconfirmed[0] ? unconfirmed[0].keyId : 'none';
|
2022-09-27 16:33:56 +00:00
|
|
|
log.info(`${logId}: Most recent signed key: ${recent}`);
|
|
|
|
log.info(`${logId}: Most recent confirmed signed key: ${recentConfirmed}`);
|
2021-09-17 18:27:53 +00:00
|
|
|
log.info(
|
2022-09-27 16:33:56 +00:00
|
|
|
`${logId}: Most recent unconfirmed signed key: ${recentUnconfirmed}`
|
2021-08-03 22:26:00 +00:00
|
|
|
);
|
2021-09-17 18:27:53 +00:00
|
|
|
log.info(
|
2022-09-27 16:33:56 +00:00
|
|
|
`${logId}: Total signed key count:`,
|
2023-07-14 16:53:20 +00:00
|
|
|
sortedKeys.length,
|
2021-08-03 22:26:00 +00:00
|
|
|
'-',
|
|
|
|
confirmed.length,
|
|
|
|
'confirmed'
|
|
|
|
);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
// Keep SIGNED_PRE_KEY_MINIMUM keys, drop if older than SIGNED_PRE_KEY_ARCHIVE_AGE
|
|
|
|
|
|
|
|
const toDelete: Array<number> = [];
|
|
|
|
sortedKeys.forEach((key, index) => {
|
|
|
|
if (index < SIGNED_PRE_KEY_MINIMUM) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const createdAt = key.created_at || 0;
|
|
|
|
|
|
|
|
if (isOlderThan(createdAt, SIGNED_PRE_KEY_ARCHIVE_AGE)) {
|
|
|
|
const timestamp = new Date(createdAt).toJSON();
|
|
|
|
const confirmedText = key.confirmed ? ' (confirmed)' : '';
|
|
|
|
log.info(
|
|
|
|
`${logId}: Removing signed prekey: ${key.keyId} with ` +
|
|
|
|
`timestamp ${timestamp}${confirmedText}`
|
|
|
|
);
|
|
|
|
toDelete.push(key.keyId);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (toDelete.length > 0) {
|
|
|
|
log.info(`${logId}: Removing ${toDelete.length} signed prekeys`);
|
2023-08-10 16:43:33 +00:00
|
|
|
await store.removeSignedPreKeys(ourServiceId, toDelete);
|
2023-07-14 16:53:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exposed only for tests
|
2023-08-10 16:43:33 +00:00
|
|
|
async _cleanLastResortKeys(serviceIdKind: ServiceIdKind): Promise<void> {
|
|
|
|
const ourServiceId =
|
|
|
|
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
2023-07-14 16:53:20 +00:00
|
|
|
const store = window.textsecure.storage.protocol;
|
2023-08-10 16:43:33 +00:00
|
|
|
const logId = `AccountManager.cleanLastResortKeys(${serviceIdKind})`;
|
2023-07-14 16:53:20 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const allKeys = store.loadKyberPreKeys(ourServiceId, {
|
|
|
|
isLastResort: true,
|
|
|
|
});
|
2023-07-14 16:53:20 +00:00
|
|
|
const sortedKeys = orderBy(allKeys, ['createdAt'], ['desc']);
|
|
|
|
const confirmed = sortedKeys.filter(key => key.isConfirmed);
|
|
|
|
const unconfirmed = sortedKeys.filter(key => !key.isConfirmed);
|
|
|
|
|
|
|
|
const recent = sortedKeys[0] ? sortedKeys[0].keyId : 'none';
|
|
|
|
const recentConfirmed = confirmed[0] ? confirmed[0].keyId : 'none';
|
|
|
|
const recentUnconfirmed = unconfirmed[0] ? unconfirmed[0].keyId : 'none';
|
|
|
|
log.info(`${logId}: Most recent last resort key: ${recent}`);
|
|
|
|
log.info(
|
|
|
|
`${logId}: Most recent confirmed last resort key: ${recentConfirmed}`
|
2021-08-03 22:26:00 +00:00
|
|
|
);
|
2023-07-14 16:53:20 +00:00
|
|
|
log.info(
|
|
|
|
`${logId}: Most recent unconfirmed last resort key: ${recentUnconfirmed}`
|
|
|
|
);
|
|
|
|
log.info(
|
|
|
|
`${logId}: Total last resort key count:`,
|
|
|
|
sortedKeys.length,
|
|
|
|
'-',
|
|
|
|
confirmed.length,
|
|
|
|
'confirmed'
|
|
|
|
);
|
|
|
|
|
|
|
|
// Keep LAST_RESORT_KEY_MINIMUM keys, drop if older than LAST_RESORT_KEY_ARCHIVE_AGE
|
|
|
|
|
|
|
|
const toDelete: Array<number> = [];
|
|
|
|
sortedKeys.forEach((key, index) => {
|
|
|
|
if (index < LAST_RESORT_KEY_MINIMUM) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const createdAt = key.createdAt || 0;
|
|
|
|
|
|
|
|
if (isOlderThan(createdAt, LAST_RESORT_KEY_ARCHIVE_AGE)) {
|
|
|
|
const timestamp = new Date(createdAt).toJSON();
|
|
|
|
const confirmedText = key.isConfirmed ? ' (confirmed)' : '';
|
|
|
|
log.info(
|
|
|
|
`${logId}: Removing last resort key: ${key.keyId} with ` +
|
|
|
|
`timestamp ${timestamp}${confirmedText}`
|
|
|
|
);
|
|
|
|
toDelete.push(key.keyId);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (toDelete.length > 0) {
|
|
|
|
log.info(`${logId}: Removing ${toDelete.length} last resort keys`);
|
2023-08-10 16:43:33 +00:00
|
|
|
await store.removeKyberPreKeys(ourServiceId, toDelete);
|
2023-07-14 16:53:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
async _cleanPreKeys(serviceIdKind: ServiceIdKind): Promise<void> {
|
|
|
|
const ourServiceId =
|
|
|
|
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
2023-07-14 16:53:20 +00:00
|
|
|
const store = window.textsecure.storage.protocol;
|
2023-08-10 16:43:33 +00:00
|
|
|
const logId = `AccountManager.cleanPreKeys(${serviceIdKind})`;
|
2023-07-14 16:53:20 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const preKeys = store.loadPreKeys(ourServiceId);
|
2023-07-14 16:53:20 +00:00
|
|
|
const toDelete: Array<number> = [];
|
|
|
|
const sortedKeys = orderBy(preKeys, ['createdAt'], ['desc']);
|
|
|
|
|
|
|
|
sortedKeys.forEach((key, index) => {
|
|
|
|
if (index < PRE_KEY_MAX_COUNT) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const createdAt = key.createdAt || 0;
|
|
|
|
|
|
|
|
if (isOlderThan(createdAt, PRE_KEY_ARCHIVE_AGE)) {
|
|
|
|
toDelete.push(key.keyId);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
log.info(`${logId}: ${sortedKeys.length} total prekeys`);
|
|
|
|
if (toDelete.length > 0) {
|
|
|
|
log.info(`${logId}: Removing ${toDelete.length} obsolete prekeys`);
|
2023-08-10 16:43:33 +00:00
|
|
|
await store.removePreKeys(ourServiceId, toDelete);
|
2023-07-14 16:53:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
async _cleanKyberPreKeys(serviceIdKind: ServiceIdKind): Promise<void> {
|
|
|
|
const ourServiceId =
|
|
|
|
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
|
2023-07-14 16:53:20 +00:00
|
|
|
const store = window.textsecure.storage.protocol;
|
2023-08-10 16:43:33 +00:00
|
|
|
const logId = `AccountManager.cleanKyberPreKeys(${serviceIdKind})`;
|
2023-07-14 16:53:20 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const preKeys = store.loadKyberPreKeys(ourServiceId, {
|
|
|
|
isLastResort: false,
|
|
|
|
});
|
2023-07-14 16:53:20 +00:00
|
|
|
const toDelete: Array<number> = [];
|
|
|
|
const sortedKeys = orderBy(preKeys, ['createdAt'], ['desc']);
|
|
|
|
|
|
|
|
sortedKeys.forEach((key, index) => {
|
|
|
|
if (index < PRE_KEY_MAX_COUNT) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const createdAt = key.createdAt || 0;
|
|
|
|
|
|
|
|
if (isOlderThan(createdAt, PRE_KEY_ARCHIVE_AGE)) {
|
|
|
|
toDelete.push(key.keyId);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
log.info(`${logId}: ${sortedKeys.length} total prekeys`);
|
|
|
|
if (toDelete.length > 0) {
|
|
|
|
log.info(`${logId}: Removing ${toDelete.length} kyber keys`);
|
2023-08-10 16:43:33 +00:00
|
|
|
await store.removeKyberPreKeys(ourServiceId, toDelete);
|
2023-07-14 16:53:20 +00:00
|
|
|
}
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 02:06:32 +00:00
|
|
|
async createAccount({
|
|
|
|
number,
|
|
|
|
verificationCode,
|
2022-03-01 23:01:21 +00:00
|
|
|
aciKeyPair,
|
2021-12-03 02:06:32 +00:00
|
|
|
pniKeyPair,
|
|
|
|
profileKey,
|
|
|
|
deviceName,
|
|
|
|
userAgent,
|
|
|
|
readReceipts,
|
|
|
|
accessKey,
|
|
|
|
}: CreateAccountOptionsType): Promise<void> {
|
2021-09-10 02:38:11 +00:00
|
|
|
const { storage } = window.textsecure;
|
2021-09-24 00:49:05 +00:00
|
|
|
let password = Bytes.toBase64(getRandomBytes(16));
|
2020-04-13 17:37:29 +00:00
|
|
|
password = password.substring(0, password.length - 2);
|
2021-04-16 23:13:13 +00:00
|
|
|
const registrationId = generateRegistrationId();
|
2022-10-05 00:50:07 +00:00
|
|
|
const pniRegistrationId = generateRegistrationId();
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2022-07-18 22:32:00 +00:00
|
|
|
const previousNumber = storage.user.getNumber();
|
2023-08-10 16:43:33 +00:00
|
|
|
const previousACI = storage.user.getAci();
|
|
|
|
const previousPNI = storage.user.getPni();
|
2020-04-13 17:37:29 +00:00
|
|
|
|
|
|
|
let encryptedDeviceName;
|
|
|
|
if (deviceName) {
|
2022-03-01 23:01:21 +00:00
|
|
|
encryptedDeviceName = this.encryptDeviceName(deviceName, aciKeyPair);
|
2020-04-13 17:37:29 +00:00
|
|
|
await this.deviceNameIsEncrypted();
|
|
|
|
}
|
|
|
|
|
2021-09-17 18:27:53 +00:00
|
|
|
log.info(
|
2020-04-13 17:37:29 +00:00
|
|
|
`createAccount: Number is ${number}, password has length: ${
|
|
|
|
password ? password.length : 'none'
|
|
|
|
}`
|
|
|
|
);
|
|
|
|
|
2022-10-05 00:50:07 +00:00
|
|
|
const response = await this.server.confirmCode({
|
2020-04-13 17:37:29 +00:00
|
|
|
number,
|
2022-10-05 00:50:07 +00:00
|
|
|
code: verificationCode,
|
|
|
|
newPassword: password,
|
2020-04-13 17:37:29 +00:00
|
|
|
registrationId,
|
2022-10-05 00:50:07 +00:00
|
|
|
pniRegistrationId,
|
|
|
|
deviceName: encryptedDeviceName,
|
|
|
|
accessKey,
|
|
|
|
});
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const ourAci = normalizeAci(response.uuid, 'createAccount');
|
2023-08-16 20:54:39 +00:00
|
|
|
strictAssert(
|
|
|
|
isUntaggedPniString(response.pni),
|
|
|
|
'Response pni must be untagged'
|
|
|
|
);
|
|
|
|
const ourPni = toTaggedPni(response.pni);
|
2021-09-10 02:38:11 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const uuidChanged = previousACI && ourAci && previousACI !== ourAci;
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-06-08 18:54:20 +00:00
|
|
|
// We only consider the number changed if we didn't have a UUID before
|
|
|
|
const numberChanged =
|
2022-07-18 22:32:00 +00:00
|
|
|
!previousACI && previousNumber && previousNumber !== number;
|
2021-06-08 18:54:20 +00:00
|
|
|
|
|
|
|
if (uuidChanged || numberChanged) {
|
|
|
|
if (uuidChanged) {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.warn(
|
2021-09-27 17:31:34 +00:00
|
|
|
'createAccount: New uuid is different from old uuid; deleting all previous data'
|
2020-04-13 17:37:29 +00:00
|
|
|
);
|
|
|
|
}
|
2021-06-08 18:54:20 +00:00
|
|
|
if (numberChanged) {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.warn(
|
2021-09-27 17:31:34 +00:00
|
|
|
'createAccount: New number is different from old number; deleting all previous data'
|
2020-04-13 17:37:29 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2021-09-10 02:38:11 +00:00
|
|
|
await storage.protocol.removeAllData();
|
2021-09-27 17:31:34 +00:00
|
|
|
log.info('createAccount: Successfully deleted previous data');
|
2020-04-13 17:37:29 +00:00
|
|
|
} catch (error) {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.error(
|
2020-04-13 17:37:29 +00:00
|
|
|
'Something went wrong deleting data from previous number',
|
2022-11-22 18:43:43 +00:00
|
|
|
Errors.toLogFormat(error)
|
2020-04-13 17:37:29 +00:00
|
|
|
);
|
|
|
|
}
|
2021-09-27 17:31:34 +00:00
|
|
|
} else {
|
|
|
|
log.info('createAccount: Erasing configuration (soft)');
|
|
|
|
await storage.protocol.removeAllConfiguration(
|
|
|
|
RemoveAllConfiguration.Soft
|
|
|
|
);
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
|
|
|
|
2021-09-27 17:31:34 +00:00
|
|
|
await senderCertificateService.clear();
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2022-07-18 22:32:00 +00:00
|
|
|
const previousUuids = [previousACI, previousPNI].filter(isNotNil);
|
|
|
|
|
|
|
|
if (previousUuids.length > 0) {
|
2021-09-10 02:38:11 +00:00
|
|
|
await Promise.all([
|
|
|
|
storage.put(
|
|
|
|
'identityKeyMap',
|
2022-07-18 22:32:00 +00:00
|
|
|
omit(storage.get('identityKeyMap') || {}, previousUuids)
|
2021-09-10 02:38:11 +00:00
|
|
|
),
|
|
|
|
storage.put(
|
|
|
|
'registrationIdMap',
|
2022-07-18 22:32:00 +00:00
|
|
|
omit(storage.get('registrationIdMap') || {}, previousUuids)
|
2021-09-10 02:38:11 +00:00
|
|
|
),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2021-07-23 17:23:50 +00:00
|
|
|
// `setCredentials` needs to be called
|
2020-04-13 17:37:29 +00:00
|
|
|
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
|
2023-01-01 11:41:40 +00:00
|
|
|
// indirectly calls `ConversationController.getConversationId()` which
|
2020-04-13 17:37:29 +00:00
|
|
|
// initializes the conversation for the given number (our number) which
|
|
|
|
// calls out to the user storage API to get the stored UUID and number
|
|
|
|
// information.
|
2021-09-10 02:38:11 +00:00
|
|
|
await storage.user.setCredentials({
|
2023-08-10 16:43:33 +00:00
|
|
|
aci: ourAci,
|
2021-12-03 02:06:32 +00:00
|
|
|
pni: ourPni,
|
2020-04-13 17:37:29 +00:00
|
|
|
number,
|
2021-07-23 17:23:50 +00:00
|
|
|
deviceId: response.deviceId ?? 1,
|
|
|
|
deviceName: deviceName ?? undefined,
|
|
|
|
password,
|
|
|
|
});
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-01-15 16:57:09 +00:00
|
|
|
// This needs to be done very early, because it changes how things are saved in the
|
|
|
|
// database. Your identity, for example, in the saveIdentityWithAttributes call
|
|
|
|
// below.
|
2022-12-05 22:46:54 +00:00
|
|
|
const { conversation } = window.ConversationController.maybeMergeContacts({
|
2023-08-10 16:43:33 +00:00
|
|
|
aci: ourAci,
|
2022-08-16 23:52:34 +00:00
|
|
|
pni: ourPni,
|
2021-01-15 16:57:09 +00:00
|
|
|
e164: number,
|
2022-01-20 22:44:25 +00:00
|
|
|
reason: 'createAccount',
|
2021-01-15 16:57:09 +00:00
|
|
|
});
|
|
|
|
|
2022-12-05 22:46:54 +00:00
|
|
|
if (!conversation) {
|
|
|
|
throw new Error('registrationDone: no conversation!');
|
2021-01-15 16:57:09 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 02:06:32 +00:00
|
|
|
const identityAttrs = {
|
2021-09-10 02:38:11 +00:00
|
|
|
firstUse: true,
|
|
|
|
timestamp: Date.now(),
|
|
|
|
verified: storage.protocol.VerifiedStatus.VERIFIED,
|
|
|
|
nonblockingApproval: true,
|
2021-12-03 02:06:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// update our own identity key, which may have changed
|
|
|
|
// if we're relinking after a reinstall on the master device
|
|
|
|
await Promise.all([
|
2023-08-10 16:43:33 +00:00
|
|
|
storage.protocol.saveIdentityWithAttributes(ourAci, {
|
2021-12-03 02:06:32 +00:00
|
|
|
...identityAttrs,
|
2022-03-01 23:01:21 +00:00
|
|
|
publicKey: aciKeyPair.pubKey,
|
2021-12-03 02:06:32 +00:00
|
|
|
}),
|
|
|
|
pniKeyPair
|
2023-08-10 16:43:33 +00:00
|
|
|
? storage.protocol.saveIdentityWithAttributes(ourPni, {
|
2021-12-03 02:06:32 +00:00
|
|
|
...identityAttrs,
|
|
|
|
publicKey: pniKeyPair.pubKey,
|
|
|
|
})
|
|
|
|
: Promise.resolve(),
|
|
|
|
]);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2021-09-10 02:38:11 +00:00
|
|
|
const identityKeyMap = {
|
|
|
|
...(storage.get('identityKeyMap') || {}),
|
2023-08-10 16:43:33 +00:00
|
|
|
[ourAci]: aciKeyPair,
|
2021-12-03 02:06:32 +00:00
|
|
|
...(pniKeyPair
|
|
|
|
? {
|
2022-07-28 16:35:29 +00:00
|
|
|
[ourPni]: pniKeyPair,
|
2021-12-03 02:06:32 +00:00
|
|
|
}
|
|
|
|
: {}),
|
2021-09-10 02:38:11 +00:00
|
|
|
};
|
|
|
|
const registrationIdMap = {
|
|
|
|
...(storage.get('registrationIdMap') || {}),
|
2023-08-10 16:43:33 +00:00
|
|
|
[ourAci]: registrationId,
|
2022-10-05 00:50:07 +00:00
|
|
|
[ourPni]: pniRegistrationId,
|
2021-09-10 02:38:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
await storage.put('identityKeyMap', identityKeyMap);
|
|
|
|
await storage.put('registrationIdMap', registrationIdMap);
|
2020-04-13 17:37:29 +00:00
|
|
|
if (profileKey) {
|
2021-05-05 16:39:16 +00:00
|
|
|
await ourProfileKeyService.set(profileKey);
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
|
|
|
if (userAgent) {
|
2021-09-10 02:38:11 +00:00
|
|
|
await storage.put('userAgent', userAgent);
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
|
|
|
|
2021-09-10 02:38:11 +00:00
|
|
|
await storage.put('read-receipt-setting', Boolean(readReceipts));
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2022-06-01 17:48:16 +00:00
|
|
|
const regionCode = getRegionCodeForNumber(number);
|
2021-09-10 02:38:11 +00:00
|
|
|
await storage.put('regionCode', regionCode);
|
|
|
|
await storage.protocol.hydrateCaches();
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
// Exposed only for testing
|
|
|
|
public async _confirmKeys(
|
|
|
|
keys: UploadKeysType,
|
2023-08-10 16:43:33 +00:00
|
|
|
serviceIdKind: ServiceIdKind
|
2022-03-25 17:36:08 +00:00
|
|
|
): Promise<void> {
|
2023-08-10 16:43:33 +00:00
|
|
|
const logId = `AccountManager.confirmKeys(${serviceIdKind})`;
|
2023-07-14 16:53:20 +00:00
|
|
|
const { storage } = window.textsecure;
|
|
|
|
const store = storage.protocol;
|
2023-08-10 16:43:33 +00:00
|
|
|
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
const updatedAt = Date.now();
|
|
|
|
const { signedPreKey, pqLastResortPreKey } = keys;
|
|
|
|
if (signedPreKey) {
|
|
|
|
log.info(`${logId}: confirming signed prekey key`, signedPreKey.keyId);
|
2023-08-10 16:43:33 +00:00
|
|
|
await store.confirmSignedPreKey(ourServiceId, signedPreKey.keyId);
|
2023-07-14 16:53:20 +00:00
|
|
|
await window.storage.put(
|
2023-08-10 16:43:33 +00:00
|
|
|
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
2023-07-14 16:53:20 +00:00
|
|
|
updatedAt
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log.info(`${logId}: signedPreKey was not uploaded, not confirming`);
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
if (pqLastResortPreKey) {
|
|
|
|
log.info(
|
|
|
|
`${logId}: confirming last resort key`,
|
|
|
|
pqLastResortPreKey.keyId
|
|
|
|
);
|
2023-08-10 16:43:33 +00:00
|
|
|
await store.confirmKyberPreKey(ourServiceId, pqLastResortPreKey.keyId);
|
2023-07-14 16:53:20 +00:00
|
|
|
await window.storage.put(
|
2023-08-10 16:43:33 +00:00
|
|
|
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind],
|
2023-07-14 16:53:20 +00:00
|
|
|
updatedAt
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log.info(`${logId}: pqLastResortPreKey was not uploaded, not confirming`);
|
|
|
|
}
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
// Very similar to maybeUpdateKeys, but will always generate prekeys and doesn't upload
|
|
|
|
async _generateKeys(
|
2022-03-25 17:36:08 +00:00
|
|
|
count: number,
|
2023-08-10 16:43:33 +00:00
|
|
|
serviceIdKind: ServiceIdKind,
|
2022-03-25 17:36:08 +00:00
|
|
|
maybeIdentityKey?: KeyPairType
|
2023-07-14 16:53:20 +00:00
|
|
|
): Promise<UploadKeysType> {
|
2023-08-10 16:43:33 +00:00
|
|
|
const logId = `AcountManager.generateKeys(${serviceIdKind})`;
|
2022-03-25 17:36:08 +00:00
|
|
|
const { storage } = window.textsecure;
|
2023-07-14 16:53:20 +00:00
|
|
|
const store = storage.protocol;
|
2023-08-16 20:54:39 +00:00
|
|
|
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
|
2020-04-13 17:37:29 +00:00
|
|
|
|
2023-08-16 20:54:39 +00:00
|
|
|
const identityKey =
|
|
|
|
maybeIdentityKey ?? store.getIdentityKeyPair(ourServiceId);
|
2022-03-25 17:36:08 +00:00
|
|
|
strictAssert(identityKey, 'generateKeys: No identity key pair!');
|
2021-04-16 23:13:13 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const preKeys = await this.generateNewPreKeys(serviceIdKind, count);
|
|
|
|
const pqPreKeys = await this.generateNewKyberPreKeys(serviceIdKind, count);
|
2023-07-14 16:53:20 +00:00
|
|
|
const pqLastResortPreKey = await this.maybeUpdateLastResortKyberKey(
|
2023-08-10 16:43:33 +00:00
|
|
|
serviceIdKind
|
2023-07-14 16:53:20 +00:00
|
|
|
);
|
2023-08-10 16:43:33 +00:00
|
|
|
const signedPreKey = await this.maybeUpdateSignedPreKey(serviceIdKind);
|
2022-03-25 17:36:08 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
log.info(
|
|
|
|
`${logId}: Generated ` +
|
|
|
|
`${preKeys.length} pre keys, ` +
|
|
|
|
`${pqPreKeys.length} kyber pre keys, ` +
|
|
|
|
`${pqLastResortPreKey ? 'a' : 'NO'} last resort kyber pre key, ` +
|
|
|
|
`and ${signedPreKey ? 'a' : 'NO'} signed pre key.`
|
|
|
|
);
|
2022-03-25 17:36:08 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
// These are primarily for the summaries they log out
|
2023-08-10 16:43:33 +00:00
|
|
|
await this._cleanPreKeys(serviceIdKind);
|
|
|
|
await this._cleanKyberPreKeys(serviceIdKind);
|
|
|
|
await this._cleanLastResortKeys(serviceIdKind);
|
|
|
|
await this._cleanSignedPreKeys(serviceIdKind);
|
2022-03-25 17:36:08 +00:00
|
|
|
|
|
|
|
return {
|
2023-07-14 16:53:20 +00:00
|
|
|
identityKey: identityKey.pubKey,
|
|
|
|
preKeys,
|
|
|
|
pqPreKeys,
|
|
|
|
pqLastResortPreKey,
|
|
|
|
signedPreKey,
|
2022-03-25 17:36:08 +00:00
|
|
|
};
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|
2020-09-24 21:53:21 +00:00
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
private async registrationDone(): Promise<void> {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.info('registration done');
|
2020-04-13 17:37:29 +00:00
|
|
|
this.dispatchEvent(new Event('registration'));
|
|
|
|
}
|
2022-07-18 22:32:00 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
async setPni(
|
|
|
|
pni: PniString,
|
|
|
|
keyMaterial?: PniKeyMaterialType
|
|
|
|
): Promise<void> {
|
2023-07-14 16:53:20 +00:00
|
|
|
const logId = `AccountManager.setPni(${pni})`;
|
2022-07-18 22:32:00 +00:00
|
|
|
const { storage } = window.textsecure;
|
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const oldPni = storage.user.getPni();
|
2022-07-28 16:35:29 +00:00
|
|
|
if (oldPni === pni && !keyMaterial) {
|
2022-07-18 22:32:00 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-07-14 16:53:20 +00:00
|
|
|
log.info(`${logId}: updating from ${oldPni}`);
|
2022-07-28 16:35:29 +00:00
|
|
|
|
2022-07-18 22:32:00 +00:00
|
|
|
if (oldPni) {
|
2023-08-10 16:43:33 +00:00
|
|
|
await storage.protocol.removeOurOldPni(oldPni);
|
2022-07-18 22:32:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
await storage.user.setPni(pni);
|
|
|
|
|
2022-07-28 16:35:29 +00:00
|
|
|
if (keyMaterial) {
|
2023-08-10 16:43:33 +00:00
|
|
|
await storage.protocol.updateOurPniKeyMaterial(pni, keyMaterial);
|
2022-07-28 16:35:29 +00:00
|
|
|
|
|
|
|
// Intentionally not awaiting since this is processed on encrypted queue
|
|
|
|
// of MessageReceiver.
|
2022-12-21 18:41:48 +00:00
|
|
|
void this.queueTask(async () => {
|
2022-07-28 16:35:29 +00:00
|
|
|
try {
|
2023-08-10 16:43:33 +00:00
|
|
|
await this.maybeUpdateKeys(ServiceIdKind.PNI);
|
2022-07-28 16:35:29 +00:00
|
|
|
} catch (error) {
|
|
|
|
log.error(
|
2023-07-14 16:53:20 +00:00
|
|
|
`${logId}: Failed to upload PNI prekeys. Moving on`,
|
2022-07-28 16:35:29 +00:00
|
|
|
Errors.toLogFormat(error)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// PNI has changed and credentials are no longer valid
|
|
|
|
await storage.put('groupCredentials', []);
|
|
|
|
} else {
|
2023-07-14 16:53:20 +00:00
|
|
|
log.warn(`${logId}: no key material`);
|
2022-07-28 16:35:29 +00:00
|
|
|
}
|
2022-07-18 22:32:00 +00:00
|
|
|
}
|
2020-04-13 17:37:29 +00:00
|
|
|
}
|