Introduce Service Id Types

Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
Fedor Indutny 2023-08-10 18:43:33 +02:00 committed by Jamie Kyle
parent 414c0a58d3
commit 366b875fd2
269 changed files with 5832 additions and 5550 deletions

View file

@ -38,7 +38,8 @@ import {
generatePreKey,
generateKyberPreKey,
} from '../Curve';
import { UUID, UUIDKind } from '../types/UUID';
import type { ServiceIdString, PniString } from '../types/ServiceId';
import { ServiceIdKind, normalizeAci, normalizePni } from '../types/ServiceId';
import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
import { ourProfileKeyService } from '../services/ourProfileKey';
import { assertDev, strictAssert } from '../util/assert';
@ -49,8 +50,8 @@ import { SignalService as Proto } from '../protobuf';
import * as log from '../logging/log';
import type { StorageAccessType } from '../types/Storage';
type StorageKeyByUuidKind = {
[kind in UUIDKind]: keyof StorageAccessType;
type StorageKeyByServiceIdKind = {
[kind in ServiceIdKind]: keyof StorageAccessType;
};
const DAY = 24 * 60 * 60 * 1000;
@ -59,44 +60,44 @@ const STARTING_KEY_ID = 1;
const PROFILE_KEY_LENGTH = 32;
const KEY_TOO_OLD_THRESHOLD = 14 * DAY;
export const KYBER_KEY_ID_KEY: StorageKeyByUuidKind = {
[UUIDKind.ACI]: 'maxKyberPreKeyId',
[UUIDKind.Unknown]: 'maxKyberPreKeyId',
[UUIDKind.PNI]: 'maxKyberPreKeyIdPNI',
export const KYBER_KEY_ID_KEY: StorageKeyByServiceIdKind = {
[ServiceIdKind.ACI]: 'maxKyberPreKeyId',
[ServiceIdKind.Unknown]: 'maxKyberPreKeyId',
[ServiceIdKind.PNI]: 'maxKyberPreKeyIdPNI',
};
const LAST_RESORT_KEY_ARCHIVE_AGE = 30 * DAY;
const LAST_RESORT_KEY_ROTATION_AGE = DAY * 1.5;
const LAST_RESORT_KEY_MINIMUM = 5;
const LAST_RESORT_KEY_UPDATE_TIME_KEY: StorageKeyByUuidKind = {
[UUIDKind.ACI]: 'lastResortKeyUpdateTime',
[UUIDKind.Unknown]: 'lastResortKeyUpdateTime',
[UUIDKind.PNI]: 'lastResortKeyUpdateTimePNI',
const LAST_RESORT_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = {
[ServiceIdKind.ACI]: 'lastResortKeyUpdateTime',
[ServiceIdKind.Unknown]: 'lastResortKeyUpdateTime',
[ServiceIdKind.PNI]: 'lastResortKeyUpdateTimePNI',
};
const PRE_KEY_ARCHIVE_AGE = 90 * DAY;
const PRE_KEY_GEN_BATCH_SIZE = 100;
const PRE_KEY_MAX_COUNT = 200;
const PRE_KEY_ID_KEY: StorageKeyByUuidKind = {
[UUIDKind.ACI]: 'maxPreKeyId',
[UUIDKind.Unknown]: 'maxPreKeyId',
[UUIDKind.PNI]: 'maxPreKeyIdPNI',
const PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = {
[ServiceIdKind.ACI]: 'maxPreKeyId',
[ServiceIdKind.Unknown]: 'maxPreKeyId',
[ServiceIdKind.PNI]: 'maxPreKeyIdPNI',
};
const PRE_KEY_MINIMUM = 10;
const SIGNED_PRE_KEY_ARCHIVE_AGE = 30 * DAY;
export const SIGNED_PRE_KEY_ID_KEY: StorageKeyByUuidKind = {
[UUIDKind.ACI]: 'signedKeyId',
[UUIDKind.Unknown]: 'signedKeyId',
[UUIDKind.PNI]: 'signedKeyIdPNI',
export const SIGNED_PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = {
[ServiceIdKind.ACI]: 'signedKeyId',
[ServiceIdKind.Unknown]: 'signedKeyId',
[ServiceIdKind.PNI]: 'signedKeyIdPNI',
};
const SIGNED_PRE_KEY_ROTATION_AGE = DAY * 1.5;
const SIGNED_PRE_KEY_MINIMUM = 5;
const SIGNED_PRE_KEY_UPDATE_TIME_KEY: StorageKeyByUuidKind = {
[UUIDKind.ACI]: 'signedKeyUpdateTime',
[UUIDKind.Unknown]: 'signedKeyUpdateTime',
[UUIDKind.PNI]: 'signedKeyUpdateTimePNI',
const SIGNED_PRE_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = {
[ServiceIdKind.ACI]: 'signedKeyUpdateTime',
[ServiceIdKind.Unknown]: 'signedKeyUpdateTime',
[ServiceIdKind.PNI]: 'signedKeyUpdateTimePNI',
};
type CreateAccountOptionsType = Readonly<{
@ -111,7 +112,10 @@ type CreateAccountOptionsType = Readonly<{
accessKey?: Uint8Array;
}>;
function getNextKeyId(kind: UUIDKind, keys: StorageKeyByUuidKind): number {
function getNextKeyId(
kind: ServiceIdKind,
keys: StorageKeyByServiceIdKind
): number {
const id = window.storage.get(keys[kind]);
if (isNumber(id)) {
@ -119,8 +123,8 @@ function getNextKeyId(kind: UUIDKind, keys: StorageKeyByUuidKind): number {
}
// For PNI ids, start with existing ACI id
if (kind === UUIDKind.PNI) {
return window.storage.get(keys[UUIDKind.ACI], STARTING_KEY_ID);
if (kind === ServiceIdKind.PNI) {
return window.storage.get(keys[ServiceIdKind.ACI], STARTING_KEY_ID);
}
return STARTING_KEY_ID;
@ -168,9 +172,9 @@ export default class AccountManager extends EventTarget {
}
async decryptDeviceName(base64: string): Promise<string> {
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
const ourAci = window.textsecure.storage.user.getCheckedAci();
const identityKey =
window.textsecure.storage.protocol.getIdentityKeyPair(ourUuid);
window.textsecure.storage.protocol.getIdentityKeyPair(ourAci);
if (!identityKey) {
throw new Error('decryptDeviceName: No identity key pair!');
}
@ -196,7 +200,7 @@ export default class AccountManager extends EventTarget {
const { storage } = window.textsecure;
const deviceName = storage.user.getDeviceName();
const identityKeyPair = storage.protocol.getIdentityKeyPair(
storage.user.getCheckedUuid()
storage.user.getCheckedAci()
);
strictAssert(
identityKeyPair !== undefined,
@ -234,14 +238,14 @@ export default class AccountManager extends EventTarget {
accessKey,
});
const uploadKeys = async (kind: UUIDKind) => {
const uploadKeys = async (kind: ServiceIdKind) => {
const keys = await this._generateKeys(PRE_KEY_GEN_BATCH_SIZE, kind);
await this.server.registerKeys(keys, kind);
await this._confirmKeys(keys, kind);
};
await uploadKeys(UUIDKind.ACI);
await uploadKeys(UUIDKind.PNI);
await uploadKeys(ServiceIdKind.ACI);
await uploadKeys(ServiceIdKind.PNI);
} finally {
this.server.finishRegistration(registrationBaton);
}
@ -344,14 +348,14 @@ export default class AccountManager extends EventTarget {
readReceipts: provisionMessage.readReceipts,
});
const uploadKeys = async (kind: UUIDKind) => {
const uploadKeys = async (kind: ServiceIdKind) => {
const keys = await this._generateKeys(PRE_KEY_GEN_BATCH_SIZE, kind);
try {
await this.server.registerKeys(keys, kind);
await this._confirmKeys(keys, kind);
} catch (error) {
if (kind === UUIDKind.PNI) {
if (kind === ServiceIdKind.PNI) {
log.error(
'Failed to upload PNI prekeys. Moving on',
Errors.toLogFormat(error)
@ -363,9 +367,9 @@ export default class AccountManager extends EventTarget {
}
};
await uploadKeys(UUIDKind.ACI);
await uploadKeys(ServiceIdKind.ACI);
if (provisionMessage.pniKeyPair) {
await uploadKeys(UUIDKind.PNI);
await uploadKeys(ServiceIdKind.PNI);
}
} finally {
this.server.finishRegistration(registrationBaton);
@ -375,12 +379,12 @@ export default class AccountManager extends EventTarget {
});
}
private getIdentityKeyOrThrow(ourUuid: UUID): KeyPairType {
private getIdentityKeyOrThrow(ourServiceId: ServiceIdString): KeyPairType {
const { storage } = window.textsecure;
const store = storage.protocol;
let identityKey: KeyPairType | undefined;
try {
identityKey = store.getIdentityKeyPair(ourUuid);
identityKey = store.getIdentityKeyPair(ourServiceId);
} catch (error) {
const errorText = Errors.toLogFormat(error);
throw new Error(
@ -396,20 +400,20 @@ export default class AccountManager extends EventTarget {
}
private async generateNewPreKeys(
uuidKind: UUIDKind,
serviceIdKind: ServiceIdKind,
count: number
): Promise<Array<UploadPreKeyType>> {
const logId = `AccountManager.generateNewPreKeys(${uuidKind})`;
const logId = `AccountManager.generateNewPreKeys(${serviceIdKind})`;
const { storage } = window.textsecure;
const store = storage.protocol;
const startId = getNextKeyId(uuidKind, PRE_KEY_ID_KEY);
const startId = getNextKeyId(serviceIdKind, PRE_KEY_ID_KEY);
log.info(`${logId}: Generating ${count} new keys starting at ${startId}`);
const ourUuid = storage.user.getCheckedUuid(uuidKind);
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
if (typeof startId !== 'number') {
throw new Error(
`${logId}: Invalid ${PRE_KEY_ID_KEY[uuidKind]} in storage`
`${logId}: Invalid ${PRE_KEY_ID_KEY[serviceIdKind]} in storage`
);
}
@ -419,8 +423,8 @@ export default class AccountManager extends EventTarget {
}
await Promise.all([
store.storePreKeys(ourUuid, toSave),
storage.put(PRE_KEY_ID_KEY[uuidKind], startId + count),
store.storePreKeys(ourServiceId, toSave),
storage.put(PRE_KEY_ID_KEY[serviceIdKind], startId + count),
]);
return toSave.map(key => ({
@ -430,24 +434,24 @@ export default class AccountManager extends EventTarget {
}
private async generateNewKyberPreKeys(
uuidKind: UUIDKind,
serviceIdKind: ServiceIdKind,
count: number
): Promise<Array<UploadKyberPreKeyType>> {
const logId = `AccountManager.generateNewKyberPreKeys(${uuidKind})`;
const logId = `AccountManager.generateNewKyberPreKeys(${serviceIdKind})`;
const { storage } = window.textsecure;
const store = storage.protocol;
const startId = getNextKeyId(uuidKind, KYBER_KEY_ID_KEY);
const startId = getNextKeyId(serviceIdKind, KYBER_KEY_ID_KEY);
log.info(`${logId}: Generating ${count} new keys starting at ${startId}`);
const ourUuid = storage.user.getCheckedUuid(uuidKind);
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
if (typeof startId !== 'number') {
throw new Error(
`${logId}: Invalid ${KYBER_KEY_ID_KEY[uuidKind]} in storage`
`${logId}: Invalid ${KYBER_KEY_ID_KEY[serviceIdKind]} in storage`
);
}
const identityKey = this.getIdentityKeyOrThrow(ourUuid);
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
const toSave: Array<Omit<KyberPreKeyType, 'id'>> = [];
const toUpload: Array<UploadKyberPreKeyType> = [];
@ -460,7 +464,7 @@ export default class AccountManager extends EventTarget {
isConfirmed: false,
isLastResort: false,
keyId,
ourUuid: ourUuid.toString(),
ourUuid: ourServiceId,
});
toUpload.push({
keyId,
@ -470,18 +474,18 @@ export default class AccountManager extends EventTarget {
}
await Promise.all([
store.storeKyberPreKeys(ourUuid, toSave),
storage.put(KYBER_KEY_ID_KEY[uuidKind], startId + count),
store.storeKyberPreKeys(ourServiceId, toSave),
storage.put(KYBER_KEY_ID_KEY[serviceIdKind], startId + count),
]);
return toUpload;
}
async maybeUpdateKeys(uuidKind: UUIDKind): Promise<void> {
const logId = `maybeUpdateKeys(${uuidKind})`;
async maybeUpdateKeys(serviceIdKind: ServiceIdKind): Promise<void> {
const logId = `maybeUpdateKeys(${serviceIdKind})`;
await this.queueTask(async () => {
const { count: preKeyCount, pqCount: kyberPreKeyCount } =
await this.server.getMyKeyCounts(uuidKind);
await this.server.getMyKeyCounts(serviceIdKind);
let preKeys: Array<UploadPreKeyType> | undefined;
if (preKeyCount < PRE_KEY_MINIMUM) {
@ -489,7 +493,7 @@ export default class AccountManager extends EventTarget {
`${logId}: Server prekey count is ${preKeyCount}, generating a new set`
);
preKeys = await this.generateNewPreKeys(
uuidKind,
serviceIdKind,
PRE_KEY_GEN_BATCH_SIZE
);
}
@ -500,15 +504,15 @@ export default class AccountManager extends EventTarget {
`${logId}: Server kyber prekey count is ${kyberPreKeyCount}, generating a new set`
);
pqPreKeys = await this.generateNewKyberPreKeys(
uuidKind,
serviceIdKind,
PRE_KEY_GEN_BATCH_SIZE
);
}
const pqLastResortPreKey = await this.maybeUpdateLastResortKyberKey(
uuidKind
serviceIdKind
);
const signedPreKey = await this.maybeUpdateSignedPreKey(uuidKind);
const signedPreKey = await this.maybeUpdateSignedPreKey(serviceIdKind);
if (
!preKeys?.length &&
@ -536,8 +540,8 @@ export default class AccountManager extends EventTarget {
log.info(`${logId}: Uploading with ${keySummary.join(', ')}`);
const { storage } = window.textsecure;
const ourUuid = storage.user.getCheckedUuid(uuidKind);
const identityKey = this.getIdentityKeyOrThrow(ourUuid);
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
const toUpload = {
identityKey: identityKey.pubKey,
preKeys,
@ -547,37 +551,37 @@ export default class AccountManager extends EventTarget {
};
try {
await this.server.registerKeys(toUpload, uuidKind);
await this.server.registerKeys(toUpload, serviceIdKind);
} catch (error) {
log.error(`${logId} upload error:`, Errors.toLogFormat(error));
throw error;
}
await this._confirmKeys(toUpload, uuidKind);
await this._confirmKeys(toUpload, serviceIdKind);
const { count: updatedPreKeyCount, pqCount: updatedKyberPreKeyCount } =
await this.server.getMyKeyCounts(uuidKind);
await this.server.getMyKeyCounts(serviceIdKind);
log.info(
`${logId}: Successfully updated; ` +
`server prekey count: ${updatedPreKeyCount}, ` +
`server kyber prekey count: ${updatedKyberPreKeyCount}`
);
await this._cleanSignedPreKeys(uuidKind);
await this._cleanLastResortKeys(uuidKind);
await this._cleanPreKeys(uuidKind);
await this._cleanKyberPreKeys(uuidKind);
await this._cleanSignedPreKeys(serviceIdKind);
await this._cleanLastResortKeys(serviceIdKind);
await this._cleanPreKeys(serviceIdKind);
await this._cleanKyberPreKeys(serviceIdKind);
});
}
areKeysOutOfDate(uuidKind: UUIDKind): boolean {
areKeysOutOfDate(serviceIdKind: ServiceIdKind): boolean {
const signedPreKeyTime = window.storage.get(
SIGNED_PRE_KEY_UPDATE_TIME_KEY[uuidKind],
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind],
0
);
const lastResortKeyTime = window.storage.get(
LAST_RESORT_KEY_UPDATE_TIME_KEY[uuidKind],
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind],
0
);
@ -592,20 +596,21 @@ export default class AccountManager extends EventTarget {
}
private async maybeUpdateSignedPreKey(
uuidKind: UUIDKind
serviceIdKind: ServiceIdKind
): Promise<UploadSignedPreKeyType | undefined> {
const logId = `AccountManager.maybeUpdateSignedPreKey(${uuidKind})`;
const logId = `AccountManager.maybeUpdateSignedPreKey(${serviceIdKind})`;
const store = window.textsecure.storage.protocol;
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
const signedKeyId = getNextKeyId(uuidKind, SIGNED_PRE_KEY_ID_KEY);
const ourServiceId =
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
const signedKeyId = getNextKeyId(serviceIdKind, SIGNED_PRE_KEY_ID_KEY);
if (typeof signedKeyId !== 'number') {
throw new Error(
`${logId}: Invalid ${SIGNED_PRE_KEY_ID_KEY[uuidKind]} in storage`
`${logId}: Invalid ${SIGNED_PRE_KEY_ID_KEY[serviceIdKind]} in storage`
);
}
const keys = await store.loadSignedPreKeys(ourUuid);
const keys = await store.loadSignedPreKeys(ourServiceId);
const sortedKeys = orderBy(keys, ['created_at'], ['desc']);
const confirmedKeys = sortedKeys.filter(key => key.confirmed);
const mostRecent = confirmedKeys[0];
@ -617,29 +622,29 @@ export default class AccountManager extends EventTarget {
`most recent was created ${lastUpdate}. No need to update.`
);
const existing = window.storage.get(
SIGNED_PRE_KEY_UPDATE_TIME_KEY[uuidKind]
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind]
);
if (lastUpdate && !existing) {
log.warn(`${logId}: Updating last update time to ${lastUpdate}`);
await window.storage.put(
SIGNED_PRE_KEY_UPDATE_TIME_KEY[uuidKind],
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind],
lastUpdate
);
}
return;
}
const identityKey = this.getIdentityKeyOrThrow(ourUuid);
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
const key = await generateSignedPreKey(identityKey, signedKeyId);
log.info(`${logId}: Saving new signed prekey`, key.keyId);
await Promise.all([
window.textsecure.storage.put(
SIGNED_PRE_KEY_ID_KEY[uuidKind],
SIGNED_PRE_KEY_ID_KEY[serviceIdKind],
signedKeyId + 1
),
store.storeSignedPreKey(ourUuid, key.keyId, key.keyPair),
store.storeSignedPreKey(ourServiceId, key.keyId, key.keyPair),
]);
return {
@ -650,20 +655,21 @@ export default class AccountManager extends EventTarget {
}
private async maybeUpdateLastResortKyberKey(
uuidKind: UUIDKind
serviceIdKind: ServiceIdKind
): Promise<UploadSignedPreKeyType | undefined> {
const logId = `maybeUpdateLastResortKyberKey(${uuidKind})`;
const logId = `maybeUpdateLastResortKyberKey(${serviceIdKind})`;
const store = window.textsecure.storage.protocol;
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
const kyberKeyId = getNextKeyId(uuidKind, KYBER_KEY_ID_KEY);
const ourServiceId =
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
const kyberKeyId = getNextKeyId(serviceIdKind, KYBER_KEY_ID_KEY);
if (typeof kyberKeyId !== 'number') {
throw new Error(
`${logId}: Invalid ${KYBER_KEY_ID_KEY[uuidKind]} in storage`
`${logId}: Invalid ${KYBER_KEY_ID_KEY[serviceIdKind]} in storage`
);
}
const keys = store.loadKyberPreKeys(ourUuid, { isLastResort: true });
const keys = store.loadKyberPreKeys(ourServiceId, { isLastResort: true });
const sortedKeys = orderBy(keys, ['createdAt'], ['desc']);
const confirmedKeys = sortedKeys.filter(key => key.isConfirmed);
const mostRecent = confirmedKeys[0];
@ -675,19 +681,19 @@ export default class AccountManager extends EventTarget {
`most recent was created ${lastUpdate}. No need to update.`
);
const existing = window.storage.get(
LAST_RESORT_KEY_UPDATE_TIME_KEY[uuidKind]
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind]
);
if (lastUpdate && !existing) {
log.warn(`${logId}: Updating last update time to ${lastUpdate}`);
await window.storage.put(
LAST_RESORT_KEY_UPDATE_TIME_KEY[uuidKind],
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind],
lastUpdate
);
}
return;
}
const identityKey = this.getIdentityKeyOrThrow(ourUuid);
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
const keyId = kyberKeyId;
const record = await generateKyberPreKey(identityKey, keyId);
@ -698,12 +704,15 @@ export default class AccountManager extends EventTarget {
isConfirmed: false,
isLastResort: true,
keyId,
ourUuid: ourUuid.toString(),
ourUuid: ourServiceId,
};
await Promise.all([
window.textsecure.storage.put(KYBER_KEY_ID_KEY[uuidKind], kyberKeyId + 1),
store.storeKyberPreKeys(ourUuid, [key]),
window.textsecure.storage.put(
KYBER_KEY_ID_KEY[serviceIdKind],
kyberKeyId + 1
),
store.storeKyberPreKeys(ourServiceId, [key]),
]);
return {
@ -714,12 +723,13 @@ export default class AccountManager extends EventTarget {
}
// Exposed only for tests
async _cleanSignedPreKeys(uuidKind: UUIDKind): Promise<void> {
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
async _cleanSignedPreKeys(serviceIdKind: ServiceIdKind): Promise<void> {
const ourServiceId =
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
const store = window.textsecure.storage.protocol;
const logId = `AccountManager.cleanSignedPreKeys(${uuidKind})`;
const logId = `AccountManager.cleanSignedPreKeys(${serviceIdKind})`;
const allKeys = store.loadSignedPreKeys(ourUuid);
const allKeys = store.loadSignedPreKeys(ourServiceId);
const sortedKeys = orderBy(allKeys, ['created_at'], ['desc']);
const confirmed = sortedKeys.filter(key => key.confirmed);
const unconfirmed = sortedKeys.filter(key => !key.confirmed);
@ -761,17 +771,20 @@ export default class AccountManager extends EventTarget {
});
if (toDelete.length > 0) {
log.info(`${logId}: Removing ${toDelete.length} signed prekeys`);
await store.removeSignedPreKeys(ourUuid, toDelete);
await store.removeSignedPreKeys(ourServiceId, toDelete);
}
}
// Exposed only for tests
async _cleanLastResortKeys(uuidKind: UUIDKind): Promise<void> {
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
async _cleanLastResortKeys(serviceIdKind: ServiceIdKind): Promise<void> {
const ourServiceId =
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
const store = window.textsecure.storage.protocol;
const logId = `AccountManager.cleanLastResortKeys(${uuidKind})`;
const logId = `AccountManager.cleanLastResortKeys(${serviceIdKind})`;
const allKeys = store.loadKyberPreKeys(ourUuid, { isLastResort: true });
const allKeys = store.loadKyberPreKeys(ourServiceId, {
isLastResort: true,
});
const sortedKeys = orderBy(allKeys, ['createdAt'], ['desc']);
const confirmed = sortedKeys.filter(key => key.isConfirmed);
const unconfirmed = sortedKeys.filter(key => !key.isConfirmed);
@ -815,16 +828,17 @@ export default class AccountManager extends EventTarget {
});
if (toDelete.length > 0) {
log.info(`${logId}: Removing ${toDelete.length} last resort keys`);
await store.removeKyberPreKeys(ourUuid, toDelete);
await store.removeKyberPreKeys(ourServiceId, toDelete);
}
}
async _cleanPreKeys(uuidKind: UUIDKind): Promise<void> {
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
async _cleanPreKeys(serviceIdKind: ServiceIdKind): Promise<void> {
const ourServiceId =
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
const store = window.textsecure.storage.protocol;
const logId = `AccountManager.cleanPreKeys(${uuidKind})`;
const logId = `AccountManager.cleanPreKeys(${serviceIdKind})`;
const preKeys = store.loadPreKeys(ourUuid);
const preKeys = store.loadPreKeys(ourServiceId);
const toDelete: Array<number> = [];
const sortedKeys = orderBy(preKeys, ['createdAt'], ['desc']);
@ -842,16 +856,19 @@ export default class AccountManager extends EventTarget {
log.info(`${logId}: ${sortedKeys.length} total prekeys`);
if (toDelete.length > 0) {
log.info(`${logId}: Removing ${toDelete.length} obsolete prekeys`);
await store.removePreKeys(ourUuid, toDelete);
await store.removePreKeys(ourServiceId, toDelete);
}
}
async _cleanKyberPreKeys(uuidKind: UUIDKind): Promise<void> {
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
async _cleanKyberPreKeys(serviceIdKind: ServiceIdKind): Promise<void> {
const ourServiceId =
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
const store = window.textsecure.storage.protocol;
const logId = `AccountManager.cleanKyberPreKeys(${uuidKind})`;
const logId = `AccountManager.cleanKyberPreKeys(${serviceIdKind})`;
const preKeys = store.loadKyberPreKeys(ourUuid, { isLastResort: false });
const preKeys = store.loadKyberPreKeys(ourServiceId, {
isLastResort: false,
});
const toDelete: Array<number> = [];
const sortedKeys = orderBy(preKeys, ['createdAt'], ['desc']);
@ -869,7 +886,7 @@ export default class AccountManager extends EventTarget {
log.info(`${logId}: ${sortedKeys.length} total prekeys`);
if (toDelete.length > 0) {
log.info(`${logId}: Removing ${toDelete.length} kyber keys`);
await store.removeKyberPreKeys(ourUuid, toDelete);
await store.removeKyberPreKeys(ourServiceId, toDelete);
}
}
@ -891,8 +908,8 @@ export default class AccountManager extends EventTarget {
const pniRegistrationId = generateRegistrationId();
const previousNumber = storage.user.getNumber();
const previousACI = storage.user.getUuid(UUIDKind.ACI)?.toString();
const previousPNI = storage.user.getUuid(UUIDKind.PNI)?.toString();
const previousACI = storage.user.getAci();
const previousPNI = storage.user.getPni();
let encryptedDeviceName;
if (deviceName) {
@ -916,10 +933,10 @@ export default class AccountManager extends EventTarget {
accessKey,
});
const ourUuid = UUID.cast(response.uuid);
const ourPni = UUID.cast(response.pni);
const ourAci = normalizeAci(response.uuid, 'createAccount');
const ourPni = normalizePni(response.pni, 'createAccount');
const uuidChanged = previousACI && ourUuid && previousACI !== ourUuid;
const uuidChanged = previousACI && ourAci && previousACI !== ourAci;
// We only consider the number changed if we didn't have a UUID before
const numberChanged =
@ -977,7 +994,7 @@ export default class AccountManager extends EventTarget {
// calls out to the user storage API to get the stored UUID and number
// information.
await storage.user.setCredentials({
uuid: ourUuid,
aci: ourAci,
pni: ourPni,
number,
deviceId: response.deviceId ?? 1,
@ -989,7 +1006,7 @@ export default class AccountManager extends EventTarget {
// database. Your identity, for example, in the saveIdentityWithAttributes call
// below.
const { conversation } = window.ConversationController.maybeMergeContacts({
aci: ourUuid,
aci: ourAci,
pni: ourPni,
e164: number,
reason: 'createAccount',
@ -1009,12 +1026,12 @@ export default class AccountManager extends EventTarget {
// update our own identity key, which may have changed
// if we're relinking after a reinstall on the master device
await Promise.all([
storage.protocol.saveIdentityWithAttributes(new UUID(ourUuid), {
storage.protocol.saveIdentityWithAttributes(ourAci, {
...identityAttrs,
publicKey: aciKeyPair.pubKey,
}),
pniKeyPair
? storage.protocol.saveIdentityWithAttributes(new UUID(ourPni), {
? storage.protocol.saveIdentityWithAttributes(ourPni, {
...identityAttrs,
publicKey: pniKeyPair.pubKey,
})
@ -1023,7 +1040,7 @@ export default class AccountManager extends EventTarget {
const identityKeyMap = {
...(storage.get('identityKeyMap') || {}),
[ourUuid]: aciKeyPair,
[ourAci]: aciKeyPair,
...(pniKeyPair
? {
[ourPni]: pniKeyPair,
@ -1032,7 +1049,7 @@ export default class AccountManager extends EventTarget {
};
const registrationIdMap = {
...(storage.get('registrationIdMap') || {}),
[ourUuid]: registrationId,
[ourAci]: registrationId,
[ourPni]: pniRegistrationId,
};
@ -1055,20 +1072,20 @@ export default class AccountManager extends EventTarget {
// Exposed only for testing
public async _confirmKeys(
keys: UploadKeysType,
uuidKind: UUIDKind
serviceIdKind: ServiceIdKind
): Promise<void> {
const logId = `AccountManager.confirmKeys(${uuidKind})`;
const logId = `AccountManager.confirmKeys(${serviceIdKind})`;
const { storage } = window.textsecure;
const store = storage.protocol;
const ourUuid = storage.user.getCheckedUuid(uuidKind);
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
const updatedAt = Date.now();
const { signedPreKey, pqLastResortPreKey } = keys;
if (signedPreKey) {
log.info(`${logId}: confirming signed prekey key`, signedPreKey.keyId);
await store.confirmSignedPreKey(ourUuid, signedPreKey.keyId);
await store.confirmSignedPreKey(ourServiceId, signedPreKey.keyId);
await window.storage.put(
SIGNED_PRE_KEY_UPDATE_TIME_KEY[uuidKind],
SIGNED_PRE_KEY_UPDATE_TIME_KEY[serviceIdKind],
updatedAt
);
} else {
@ -1080,9 +1097,9 @@ export default class AccountManager extends EventTarget {
`${logId}: confirming last resort key`,
pqLastResortPreKey.keyId
);
await store.confirmKyberPreKey(ourUuid, pqLastResortPreKey.keyId);
await store.confirmKyberPreKey(ourServiceId, pqLastResortPreKey.keyId);
await window.storage.put(
LAST_RESORT_KEY_UPDATE_TIME_KEY[uuidKind],
LAST_RESORT_KEY_UPDATE_TIME_KEY[serviceIdKind],
updatedAt
);
} else {
@ -1093,23 +1110,23 @@ export default class AccountManager extends EventTarget {
// Very similar to maybeUpdateKeys, but will always generate prekeys and doesn't upload
async _generateKeys(
count: number,
uuidKind: UUIDKind,
serviceIdKind: ServiceIdKind,
maybeIdentityKey?: KeyPairType
): Promise<UploadKeysType> {
const logId = `AcountManager.generateKeys(${uuidKind})`;
const logId = `AcountManager.generateKeys(${serviceIdKind})`;
const { storage } = window.textsecure;
const store = storage.protocol;
const ourUuid = storage.user.getCheckedUuid(uuidKind);
const ourUuid = storage.user.getCheckedServiceId(serviceIdKind);
const identityKey = maybeIdentityKey ?? store.getIdentityKeyPair(ourUuid);
strictAssert(identityKey, 'generateKeys: No identity key pair!');
const preKeys = await this.generateNewPreKeys(uuidKind, count);
const pqPreKeys = await this.generateNewKyberPreKeys(uuidKind, count);
const preKeys = await this.generateNewPreKeys(serviceIdKind, count);
const pqPreKeys = await this.generateNewKyberPreKeys(serviceIdKind, count);
const pqLastResortPreKey = await this.maybeUpdateLastResortKyberKey(
uuidKind
serviceIdKind
);
const signedPreKey = await this.maybeUpdateSignedPreKey(uuidKind);
const signedPreKey = await this.maybeUpdateSignedPreKey(serviceIdKind);
log.info(
`${logId}: Generated ` +
@ -1120,10 +1137,10 @@ export default class AccountManager extends EventTarget {
);
// These are primarily for the summaries they log out
await this._cleanPreKeys(uuidKind);
await this._cleanKyberPreKeys(uuidKind);
await this._cleanLastResortKeys(uuidKind);
await this._cleanSignedPreKeys(uuidKind);
await this._cleanPreKeys(serviceIdKind);
await this._cleanKyberPreKeys(serviceIdKind);
await this._cleanLastResortKeys(serviceIdKind);
await this._cleanSignedPreKeys(serviceIdKind);
return {
identityKey: identityKey.pubKey,
@ -1139,11 +1156,14 @@ export default class AccountManager extends EventTarget {
this.dispatchEvent(new Event('registration'));
}
async setPni(pni: string, keyMaterial?: PniKeyMaterialType): Promise<void> {
async setPni(
pni: PniString,
keyMaterial?: PniKeyMaterialType
): Promise<void> {
const logId = `AccountManager.setPni(${pni})`;
const { storage } = window.textsecure;
const oldPni = storage.user.getUuid(UUIDKind.PNI)?.toString();
const oldPni = storage.user.getPni();
if (oldPni === pni && !keyMaterial) {
return;
}
@ -1151,22 +1171,19 @@ export default class AccountManager extends EventTarget {
log.info(`${logId}: updating from ${oldPni}`);
if (oldPni) {
await storage.protocol.removeOurOldPni(new UUID(oldPni));
await storage.protocol.removeOurOldPni(oldPni);
}
await storage.user.setPni(pni);
if (keyMaterial) {
await storage.protocol.updateOurPniKeyMaterial(
new UUID(pni),
keyMaterial
);
await storage.protocol.updateOurPniKeyMaterial(pni, keyMaterial);
// Intentionally not awaiting since this is processed on encrypted queue
// of MessageReceiver.
void this.queueTask(async () => {
try {
await this.maybeUpdateKeys(UUIDKind.PNI);
await this.maybeUpdateKeys(ServiceIdKind.PNI);
} catch (error) {
log.error(
`${logId}: Failed to upload PNI prekeys. Moving on`,