Request and handle PniIdentity sync message
This commit is contained in:
parent
5a107e1bc3
commit
a0ae7c1aa2
7 changed files with 245 additions and 74 deletions
|
@ -407,6 +407,7 @@ message SyncMessage {
|
||||||
BLOCKED = 3;
|
BLOCKED = 3;
|
||||||
CONFIGURATION = 4;
|
CONFIGURATION = 4;
|
||||||
KEYS = 5;
|
KEYS = 5;
|
||||||
|
PNI_IDENTITY = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
|
@ -416,6 +417,11 @@ message SyncMessage {
|
||||||
optional bytes storageService = 1;
|
optional bytes storageService = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message PniIdentity {
|
||||||
|
optional bytes publicKey = 1;
|
||||||
|
optional bytes privateKey = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message Read {
|
message Read {
|
||||||
optional string sender = 1;
|
optional string sender = 1;
|
||||||
optional string senderUuid = 3;
|
optional string senderUuid = 3;
|
||||||
|
@ -496,6 +502,7 @@ message SyncMessage {
|
||||||
optional MessageRequestResponse messageRequestResponse = 14;
|
optional MessageRequestResponse messageRequestResponse = 14;
|
||||||
reserved 15; // not yet added
|
reserved 15; // not yet added
|
||||||
repeated Viewed viewed = 16;
|
repeated Viewed viewed = 16;
|
||||||
|
optional PniIdentity pniIdentity = 17;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AttachmentPointer {
|
message AttachmentPointer {
|
||||||
|
|
|
@ -68,6 +68,7 @@ import type {
|
||||||
FetchLatestEvent,
|
FetchLatestEvent,
|
||||||
GroupEvent,
|
GroupEvent,
|
||||||
KeysEvent,
|
KeysEvent,
|
||||||
|
PNIIdentityEvent,
|
||||||
MessageEvent,
|
MessageEvent,
|
||||||
MessageEventData,
|
MessageEventData,
|
||||||
MessageRequestResponseEvent,
|
MessageRequestResponseEvent,
|
||||||
|
@ -369,6 +370,10 @@ export async function startApp(): Promise<void> {
|
||||||
queuedEventListener(onFetchLatestSync)
|
queuedEventListener(onFetchLatestSync)
|
||||||
);
|
);
|
||||||
messageReceiver.addEventListener('keys', queuedEventListener(onKeysSync));
|
messageReceiver.addEventListener('keys', queuedEventListener(onKeysSync));
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'pniIdentity',
|
||||||
|
queuedEventListener(onPNIIdentitySync)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ourProfileKeyService.initialize(window.storage);
|
ourProfileKeyService.initialize(window.storage);
|
||||||
|
@ -2259,6 +2264,8 @@ export async function startApp(): Promise<void> {
|
||||||
window.waitForEmptyEventQueue = waitForEmptyEventQueue;
|
window.waitForEmptyEventQueue = waitForEmptyEventQueue;
|
||||||
|
|
||||||
async function onEmpty() {
|
async function onEmpty() {
|
||||||
|
const { storage, messaging } = window.textsecure;
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
window.waitForAllBatchers(),
|
window.waitForAllBatchers(),
|
||||||
window.flushAllWaitBatchers(),
|
window.flushAllWaitBatchers(),
|
||||||
|
@ -2332,7 +2339,7 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await window.Signal.Data.saveMessages(messagesToSave, {
|
await window.Signal.Data.saveMessages(messagesToSave, {
|
||||||
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
ourUuid: storage.user.getCheckedUuid().toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process crash reports if any
|
// Process crash reports if any
|
||||||
|
@ -2348,7 +2355,7 @@ export async function startApp(): Promise<void> {
|
||||||
routineProfileRefresh({
|
routineProfileRefresh({
|
||||||
allConversations: window.ConversationController.getAll(),
|
allConversations: window.ConversationController.getAll(),
|
||||||
ourConversationId,
|
ourConversationId,
|
||||||
storage: window.storage,
|
storage,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
assert(
|
assert(
|
||||||
|
@ -2356,6 +2363,17 @@ export async function startApp(): Promise<void> {
|
||||||
'Failed to fetch our conversation ID. Skipping routine profile refresh'
|
'Failed to fetch our conversation ID. Skipping routine profile refresh'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure we have the PNI identity
|
||||||
|
|
||||||
|
const pni = storage.user.getCheckedUuid(UUIDKind.PNI);
|
||||||
|
const pniIdentity = await storage.protocol.getIdentityKeyPair(pni);
|
||||||
|
if (!pniIdentity) {
|
||||||
|
log.info('Requesting PNI identity sync');
|
||||||
|
await singleProtoJobQueue.add(
|
||||||
|
messaging.getRequestPniIdentitySyncMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let initialStartupCount = 0;
|
let initialStartupCount = 0;
|
||||||
|
@ -3486,6 +3504,15 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onPNIIdentitySync(ev: PNIIdentityEvent) {
|
||||||
|
ev.confirm();
|
||||||
|
|
||||||
|
log.info('onPNIIdentitySync: updating PNI keys');
|
||||||
|
const manager = window.getAccountManager();
|
||||||
|
const { privateKey: privKey, publicKey: pubKey } = ev.data;
|
||||||
|
await manager.updatePNIIdentity({ privKey, pubKey });
|
||||||
|
}
|
||||||
|
|
||||||
async function onMessageRequestResponse(ev: MessageRequestResponseEvent) {
|
async function onMessageRequestResponse(ev: MessageRequestResponseEvent) {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
// Copyright 2020-2021 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable @typescript-eslint/ban-types */
|
|
||||||
/* eslint-disable more/no-then */
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
import EventTarget from './EventTarget';
|
import EventTarget from './EventTarget';
|
||||||
import type { WebAPIType } from './WebAPI';
|
import type { WebAPIType, GroupCredentialType } from './WebAPI';
|
||||||
import { HTTPError } from './Errors';
|
import { HTTPError } from './Errors';
|
||||||
import type { KeyPairType } from './Types.d';
|
import type { KeyPairType } from './Types.d';
|
||||||
import ProvisioningCipher from './ProvisioningCipher';
|
import ProvisioningCipher from './ProvisioningCipher';
|
||||||
|
@ -96,15 +92,15 @@ export default class AccountManager extends EventTarget {
|
||||||
this.pending = Promise.resolve();
|
this.pending = Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestVoiceVerification(number: string, token: string) {
|
async requestVoiceVerification(number: string, token: string): Promise<void> {
|
||||||
return this.server.requestVerificationVoice(number, token);
|
return this.server.requestVerificationVoice(number, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestSMSVerification(number: string, token: string) {
|
async requestSMSVerification(number: string, token: string): Promise<void> {
|
||||||
return this.server.requestVerificationSMS(number, token);
|
return this.server.requestVerificationSMS(number, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptDeviceName(name: string, identityKey: KeyPairType) {
|
encryptDeviceName(name: string, identityKey: KeyPairType): string | null {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +115,7 @@ export default class AccountManager extends EventTarget {
|
||||||
return Bytes.toBase64(bytes);
|
return Bytes.toBase64(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
async decryptDeviceName(base64: string) {
|
async decryptDeviceName(base64: string): Promise<string> {
|
||||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const identityKey =
|
const identityKey =
|
||||||
await window.textsecure.storage.protocol.getIdentityKeyPair(ourUuid);
|
await window.textsecure.storage.protocol.getIdentityKeyPair(ourUuid);
|
||||||
|
@ -139,7 +135,7 @@ export default class AccountManager extends EventTarget {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
async maybeUpdateDeviceName() {
|
async maybeUpdateDeviceName(): Promise<void> {
|
||||||
const isNameEncrypted =
|
const isNameEncrypted =
|
||||||
window.textsecure.storage.user.getDeviceNameEncrypted();
|
window.textsecure.storage.user.getDeviceNameEncrypted();
|
||||||
if (isNameEncrypted) {
|
if (isNameEncrypted) {
|
||||||
|
@ -161,11 +157,14 @@ export default class AccountManager extends EventTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deviceNameIsEncrypted() {
|
async deviceNameIsEncrypted(): Promise<void> {
|
||||||
await window.textsecure.storage.user.setDeviceNameEncrypted();
|
await window.textsecure.storage.user.setDeviceNameEncrypted();
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerSingleDevice(number: string, verificationCode: string) {
|
async registerSingleDevice(
|
||||||
|
number: string,
|
||||||
|
verificationCode: string
|
||||||
|
): Promise<void> {
|
||||||
return this.queueTask(async () => {
|
return this.queueTask(async () => {
|
||||||
const aciKeyPair = generateKeyPair();
|
const aciKeyPair = generateKeyPair();
|
||||||
const pniKeyPair = generateKeyPair();
|
const pniKeyPair = generateKeyPair();
|
||||||
|
@ -203,9 +202,9 @@ export default class AccountManager extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerSecondDevice(
|
async registerSecondDevice(
|
||||||
setProvisioningUrl: (url: string) => unknown,
|
setProvisioningUrl: (url: string) => void,
|
||||||
confirmNumber: (number?: string) => Promise<string>
|
confirmNumber: (number?: string) => Promise<string>
|
||||||
) {
|
): Promise<void> {
|
||||||
const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
|
const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
|
||||||
const provisioningCipher = new ProvisioningCipher();
|
const provisioningCipher = new ProvisioningCipher();
|
||||||
const pubKey = await provisioningCipher.getPublicKey();
|
const pubKey = await provisioningCipher.getPublicKey();
|
||||||
|
@ -323,7 +322,7 @@ export default class AccountManager extends EventTarget {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshPreKeys(uuidKind: UUIDKind) {
|
async refreshPreKeys(uuidKind: UUIDKind): Promise<void> {
|
||||||
return this.queueTask(async () => {
|
return this.queueTask(async () => {
|
||||||
const preKeyCount = await this.server.getMyKeys(uuidKind);
|
const preKeyCount = await this.server.getMyKeys(uuidKind);
|
||||||
log.info(`prekey count ${preKeyCount}`);
|
log.info(`prekey count ${preKeyCount}`);
|
||||||
|
@ -335,7 +334,7 @@ export default class AccountManager extends EventTarget {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async rotateSignedPreKey(uuidKind: UUIDKind) {
|
async rotateSignedPreKey(uuidKind: UUIDKind): Promise<void> {
|
||||||
return this.queueTask(async () => {
|
return this.queueTask(async () => {
|
||||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
|
||||||
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
|
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
|
||||||
|
@ -447,14 +446,14 @@ export default class AccountManager extends EventTarget {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async queueTask(task: () => Promise<any>) {
|
async queueTask<T>(task: () => Promise<T>): Promise<T> {
|
||||||
this.pendingQueue = this.pendingQueue || new PQueue({ concurrency: 1 });
|
this.pendingQueue = this.pendingQueue || new PQueue({ concurrency: 1 });
|
||||||
const taskWithTimeout = createTaskWithTimeout(task, 'AccountManager task');
|
const taskWithTimeout = createTaskWithTimeout(task, 'AccountManager task');
|
||||||
|
|
||||||
return this.pendingQueue.add(taskWithTimeout);
|
return this.pendingQueue.add(taskWithTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanSignedPreKeys() {
|
async cleanSignedPreKeys(): Promise<void> {
|
||||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const store = window.textsecure.storage.protocol;
|
const store = window.textsecure.storage.protocol;
|
||||||
|
|
||||||
|
@ -662,7 +661,8 @@ export default class AccountManager extends EventTarget {
|
||||||
const registrationIdMap = {
|
const registrationIdMap = {
|
||||||
...(storage.get('registrationIdMap') || {}),
|
...(storage.get('registrationIdMap') || {}),
|
||||||
[ourUuid]: registrationId,
|
[ourUuid]: registrationId,
|
||||||
// TODO: DESKTOP-2825
|
|
||||||
|
// TODO: DESKTOP-3318
|
||||||
[ourPni]: registrationId,
|
[ourPni]: registrationId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -683,7 +683,7 @@ export default class AccountManager extends EventTarget {
|
||||||
await storage.protocol.hydrateCaches();
|
await storage.protocol.hydrateCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearSessionsAndPreKeys() {
|
async clearSessionsAndPreKeys(): Promise<void> {
|
||||||
const store = window.textsecure.storage.protocol;
|
const store = window.textsecure.storage.protocol;
|
||||||
|
|
||||||
log.info('clearing all sessions, prekeys, and signed prekeys');
|
log.info('clearing all sessions, prekeys, and signed prekeys');
|
||||||
|
@ -694,16 +694,68 @@ export default class AccountManager extends EventTarget {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePNIIdentity(identityKeyPair: KeyPairType): Promise<void> {
|
||||||
|
const { storage } = window.textsecure;
|
||||||
|
|
||||||
|
log.info('AccountManager.updatePNIIdentity: generating new keys');
|
||||||
|
|
||||||
|
return this.queueTask(async () => {
|
||||||
|
const keys = await this.generateKeys(
|
||||||
|
SIGNED_KEY_GEN_BATCH_SIZE,
|
||||||
|
UUIDKind.PNI,
|
||||||
|
identityKeyPair
|
||||||
|
);
|
||||||
|
await this.server.registerKeys(keys, UUIDKind.PNI);
|
||||||
|
await this.confirmKeys(keys, UUIDKind.PNI);
|
||||||
|
|
||||||
|
// Server has accepted our keys which means we have the latest PNI identity
|
||||||
|
// now that doesn't conflict the PNI identity of the primary device.
|
||||||
|
log.info(
|
||||||
|
'AccountManager.updatePNIIdentity: updating identity key ' +
|
||||||
|
'and registration id'
|
||||||
|
);
|
||||||
|
const { pubKey, privKey } = identityKeyPair;
|
||||||
|
|
||||||
|
const pni = storage.user.getCheckedUuid(UUIDKind.PNI);
|
||||||
|
const identityKeyMap = {
|
||||||
|
...(storage.get('identityKeyMap') || {}),
|
||||||
|
[pni.toString()]: {
|
||||||
|
pubKey: Bytes.toBase64(pubKey),
|
||||||
|
privKey: Bytes.toBase64(privKey),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const aci = storage.user.getCheckedUuid(UUIDKind.ACI);
|
||||||
|
const oldRegistrationIdMap = storage.get('registrationIdMap') || {};
|
||||||
|
const registrationIdMap = {
|
||||||
|
...oldRegistrationIdMap,
|
||||||
|
|
||||||
|
// TODO: DESKTOP-3318
|
||||||
|
[pni.toString()]: oldRegistrationIdMap[aci.toString()],
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
storage.put('identityKeyMap', identityKeyMap),
|
||||||
|
storage.put('registrationIdMap', registrationIdMap),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await storage.protocol.hydrateCaches();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async getGroupCredentials(
|
async getGroupCredentials(
|
||||||
startDay: number,
|
startDay: number,
|
||||||
endDay: number,
|
endDay: number,
|
||||||
uuidKind: UUIDKind
|
uuidKind: UUIDKind
|
||||||
) {
|
): Promise<Array<GroupCredentialType>> {
|
||||||
return this.server.getGroupCredentials(startDay, endDay, uuidKind);
|
return this.server.getGroupCredentials(startDay, endDay, uuidKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Takes the same object returned by generateKeys
|
// Takes the same object returned by generateKeys
|
||||||
async confirmKeys(keys: GeneratedKeysType, uuidKind: UUIDKind) {
|
async confirmKeys(
|
||||||
|
keys: GeneratedKeysType,
|
||||||
|
uuidKind: UUIDKind
|
||||||
|
): Promise<void> {
|
||||||
const store = window.textsecure.storage.protocol;
|
const store = window.textsecure.storage.protocol;
|
||||||
const key = keys.signedPreKey;
|
const key = keys.signedPreKey;
|
||||||
const confirmed = true;
|
const confirmed = true;
|
||||||
|
@ -720,10 +772,16 @@ export default class AccountManager extends EventTarget {
|
||||||
await store.storeSignedPreKey(ourUuid, key.keyId, key.keyPair, confirmed);
|
await store.storeSignedPreKey(ourUuid, key.keyId, key.keyPair, confirmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateKeys(count: number, uuidKind: UUIDKind) {
|
async generateKeys(
|
||||||
const startId = window.textsecure.storage.get('maxPreKeyId', 1);
|
count: number,
|
||||||
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
|
uuidKind: UUIDKind,
|
||||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
|
maybeIdentityKey?: KeyPairType
|
||||||
|
): Promise<GeneratedKeysType> {
|
||||||
|
const { storage } = window.textsecure;
|
||||||
|
|
||||||
|
const startId = storage.get('maxPreKeyId', 1);
|
||||||
|
const signedKeyId = storage.get('signedKeyId', 1);
|
||||||
|
const ourUuid = storage.user.getCheckedUuid(uuidKind);
|
||||||
|
|
||||||
if (typeof startId !== 'number') {
|
if (typeof startId !== 'number') {
|
||||||
throw new Error('Invalid maxPreKeyId');
|
throw new Error('Invalid maxPreKeyId');
|
||||||
|
@ -732,60 +790,58 @@ export default class AccountManager extends EventTarget {
|
||||||
throw new Error('Invalid signedKeyId');
|
throw new Error('Invalid signedKeyId');
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = window.textsecure.storage.protocol;
|
const store = storage.protocol;
|
||||||
return store.getIdentityKeyPair(ourUuid).then(async identityKey => {
|
const identityKey =
|
||||||
if (!identityKey) {
|
maybeIdentityKey ?? (await store.getIdentityKeyPair(ourUuid));
|
||||||
throw new Error('generateKeys: No identity key pair!');
|
strictAssert(identityKey, 'generateKeys: No identity key pair!');
|
||||||
}
|
|
||||||
|
|
||||||
const result: any = {
|
const result: Omit<GeneratedKeysType, 'signedPreKey'> = {
|
||||||
preKeys: [],
|
preKeys: [],
|
||||||
identityKey: identityKey.pubKey,
|
identityKey: identityKey.pubKey,
|
||||||
|
};
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (let keyId = startId; keyId < startId + count; keyId += 1) {
|
||||||
|
promises.push(
|
||||||
|
(async () => {
|
||||||
|
const res = generatePreKey(keyId);
|
||||||
|
await store.storePreKey(ourUuid, res.keyId, res.keyPair);
|
||||||
|
result.preKeys.push({
|
||||||
|
keyId: res.keyId,
|
||||||
|
publicKey: res.keyPair.pubKey,
|
||||||
|
});
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const signedPreKey = (async () => {
|
||||||
|
const res = generateSignedPreKey(identityKey, signedKeyId);
|
||||||
|
await store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair);
|
||||||
|
return {
|
||||||
|
keyId: res.keyId,
|
||||||
|
publicKey: res.keyPair.pubKey,
|
||||||
|
signature: res.signature,
|
||||||
|
// server.registerKeys doesn't use keyPair, confirmKeys does
|
||||||
|
keyPair: res.keyPair,
|
||||||
};
|
};
|
||||||
const promises = [];
|
})();
|
||||||
|
|
||||||
for (let keyId = startId; keyId < startId + count; keyId += 1) {
|
promises.push(signedPreKey);
|
||||||
promises.push(
|
promises.push(storage.put('maxPreKeyId', startId + count));
|
||||||
Promise.resolve(generatePreKey(keyId)).then(async res => {
|
promises.push(storage.put('signedKeyId', signedKeyId + 1));
|
||||||
await store.storePreKey(ourUuid, res.keyId, res.keyPair);
|
|
||||||
result.preKeys.push({
|
|
||||||
keyId: res.keyId,
|
|
||||||
publicKey: res.keyPair.pubKey,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
promises.push(
|
await Promise.all(promises);
|
||||||
Promise.resolve(generateSignedPreKey(identityKey, signedKeyId)).then(
|
|
||||||
async res => {
|
|
||||||
await store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair);
|
|
||||||
result.signedPreKey = {
|
|
||||||
keyId: res.keyId,
|
|
||||||
publicKey: res.keyPair.pubKey,
|
|
||||||
signature: res.signature,
|
|
||||||
// server.registerKeys doesn't use keyPair, confirmKeys does
|
|
||||||
keyPair: res.keyPair,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
promises.push(
|
// This is primarily for the signed prekey summary it logs out
|
||||||
window.textsecure.storage.put('maxPreKeyId', startId + count)
|
this.cleanSignedPreKeys();
|
||||||
);
|
|
||||||
promises.push(
|
|
||||||
window.textsecure.storage.put('signedKeyId', signedKeyId + 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
return Promise.all(promises).then(async () =>
|
return {
|
||||||
// This is primarily for the signed prekey summary it logs out
|
...result,
|
||||||
this.cleanSignedPreKeys().then(() => result as GeneratedKeysType)
|
signedPreKey: await signedPreKey,
|
||||||
);
|
};
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async registrationDone() {
|
async registrationDone(): Promise<void> {
|
||||||
log.info('registration done');
|
log.info('registration done');
|
||||||
this.dispatchEvent(new Event('registration'));
|
this.dispatchEvent(new Event('registration'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,7 @@ import {
|
||||||
MessageRequestResponseEvent,
|
MessageRequestResponseEvent,
|
||||||
FetchLatestEvent,
|
FetchLatestEvent,
|
||||||
KeysEvent,
|
KeysEvent,
|
||||||
|
PNIIdentityEvent,
|
||||||
StickerPackEvent,
|
StickerPackEvent,
|
||||||
VerifiedEvent,
|
VerifiedEvent,
|
||||||
ReadSyncEvent,
|
ReadSyncEvent,
|
||||||
|
@ -193,6 +194,8 @@ export default class MessageReceiver
|
||||||
|
|
||||||
private stoppingProcessing?: boolean;
|
private stoppingProcessing?: boolean;
|
||||||
|
|
||||||
|
private pendingPNIIdentityEvent?: PNIIdentityEvent;
|
||||||
|
|
||||||
constructor({ server, storage, serverTrustRoot }: MessageReceiverOptions) {
|
constructor({ server, storage, serverTrustRoot }: MessageReceiverOptions) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -467,6 +470,11 @@ export default class MessageReceiver
|
||||||
handler: (ev: KeysEvent) => void
|
handler: (ev: KeysEvent) => void
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
|
public override addEventListener(
|
||||||
|
name: 'pniIdentity',
|
||||||
|
handler: (ev: PNIIdentityEvent) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
public override addEventListener(
|
public override addEventListener(
|
||||||
name: 'sticker-pack',
|
name: 'sticker-pack',
|
||||||
handler: (ev: StickerPackEvent) => void
|
handler: (ev: StickerPackEvent) => void
|
||||||
|
@ -598,6 +606,13 @@ export default class MessageReceiver
|
||||||
this.isEmptied = true;
|
this.isEmptied = true;
|
||||||
|
|
||||||
this.maybeScheduleRetryTimeout();
|
this.maybeScheduleRetryTimeout();
|
||||||
|
|
||||||
|
// Emit PNI identity event after processing the queue
|
||||||
|
const { pendingPNIIdentityEvent } = this;
|
||||||
|
this.pendingPNIIdentityEvent = undefined;
|
||||||
|
if (pendingPNIIdentityEvent) {
|
||||||
|
await this.dispatchAndWait(pendingPNIIdentityEvent);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const waitForDecryptedQueue = async () => {
|
const waitForDecryptedQueue = async () => {
|
||||||
|
@ -2474,6 +2489,9 @@ export default class MessageReceiver
|
||||||
if (syncMessage.keys) {
|
if (syncMessage.keys) {
|
||||||
return this.handleKeys(envelope, syncMessage.keys);
|
return this.handleKeys(envelope, syncMessage.keys);
|
||||||
}
|
}
|
||||||
|
if (syncMessage.pniIdentity) {
|
||||||
|
return this.handlePNIIdentity(envelope, syncMessage.pniIdentity);
|
||||||
|
}
|
||||||
if (syncMessage.viewed && syncMessage.viewed.length) {
|
if (syncMessage.viewed && syncMessage.viewed.length) {
|
||||||
return this.handleViewed(envelope, syncMessage.viewed);
|
return this.handleViewed(envelope, syncMessage.viewed);
|
||||||
}
|
}
|
||||||
|
@ -2591,6 +2609,31 @@ export default class MessageReceiver
|
||||||
return this.dispatchAndWait(ev);
|
return this.dispatchAndWait(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handlePNIIdentity(
|
||||||
|
envelope: ProcessedEnvelope,
|
||||||
|
{ publicKey, privateKey }: Proto.SyncMessage.IPniIdentity
|
||||||
|
): Promise<void> {
|
||||||
|
log.info('MessageReceiver: got pni identity sync message');
|
||||||
|
|
||||||
|
if (!publicKey || !privateKey) {
|
||||||
|
log.warn('MessageReceiver: empty pni identity sync message');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ev = new PNIIdentityEvent(
|
||||||
|
{ publicKey, privateKey },
|
||||||
|
this.removeFromCache.bind(this, envelope)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.isEmptied) {
|
||||||
|
log.info('MessageReceiver: emitting pni identity sync message');
|
||||||
|
return this.dispatchAndWait(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('MessageReceiver: scheduling pni identity sync message');
|
||||||
|
this.pendingPNIIdentityEvent = ev;
|
||||||
|
}
|
||||||
|
|
||||||
private async handleStickerPackOperation(
|
private async handleStickerPackOperation(
|
||||||
envelope: ProcessedEnvelope,
|
envelope: ProcessedEnvelope,
|
||||||
operations: Array<Proto.SyncMessage.IStickerPackOperation>
|
operations: Array<Proto.SyncMessage.IStickerPackOperation>
|
||||||
|
|
|
@ -1266,6 +1266,29 @@ export default class MessageSender {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRequestPniIdentitySyncMessage(): SingleProtoJobData {
|
||||||
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
|
|
||||||
|
const request = new Proto.SyncMessage.Request();
|
||||||
|
request.type = Proto.SyncMessage.Request.Type.PNI_IDENTITY;
|
||||||
|
const syncMessage = this.createSyncMessage();
|
||||||
|
syncMessage.request = request;
|
||||||
|
const contentMessage = new Proto.Content();
|
||||||
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
|
return {
|
||||||
|
contentHint: ContentHint.RESENDABLE,
|
||||||
|
identifier: myUuid.toString(),
|
||||||
|
isSyncMessage: true,
|
||||||
|
protoBase64: Bytes.toBase64(
|
||||||
|
Proto.Content.encode(contentMessage).finish()
|
||||||
|
),
|
||||||
|
type: 'pniIdentitySyncRequest',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getFetchManifestSyncMessage(): SingleProtoJobData {
|
getFetchManifestSyncMessage(): SingleProtoJobData {
|
||||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
|
|
||||||
|
|
|
@ -349,6 +349,20 @@ export class KeysEvent extends ConfirmableEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PNIIdentityEventData = Readonly<{
|
||||||
|
publicKey: Uint8Array;
|
||||||
|
privateKey: Uint8Array;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class PNIIdentityEvent extends ConfirmableEvent {
|
||||||
|
constructor(
|
||||||
|
public readonly data: PNIIdentityEventData,
|
||||||
|
confirm: ConfirmCallback
|
||||||
|
) {
|
||||||
|
super('pniIdentity', confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type StickerPackEventData = Readonly<{
|
export type StickerPackEventData = Readonly<{
|
||||||
id?: string;
|
id?: string;
|
||||||
key?: string;
|
key?: string;
|
||||||
|
|
|
@ -18,6 +18,7 @@ const { insertSentProto, updateConversation } = dataInterface;
|
||||||
|
|
||||||
export const sendTypesEnum = z.enum([
|
export const sendTypesEnum = z.enum([
|
||||||
'blockSyncRequest',
|
'blockSyncRequest',
|
||||||
|
'pniIdentitySyncRequest',
|
||||||
'callingMessage', // excluded from send log
|
'callingMessage', // excluded from send log
|
||||||
'configurationSyncRequest',
|
'configurationSyncRequest',
|
||||||
'contactSyncRequest',
|
'contactSyncRequest',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue