UUID-keyed lookups in SignalProtocolStore

This commit is contained in:
Fedor Indutny 2021-09-09 19:38:11 -07:00 committed by GitHub
parent 6323aedd9b
commit c7e7d55af4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 2094 additions and 1447 deletions

View file

@ -7,6 +7,7 @@
/* eslint-disable class-methods-use-this */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import PQueue from 'p-queue';
import { omit } from 'lodash';
import EventTarget from './EventTarget';
import { WebAPIType } from './WebAPI';
@ -21,6 +22,7 @@ import {
generateRegistrationId,
getRandomBytes,
typedArrayToArrayBuffer,
arrayBufferToBase64,
} from '../Crypto';
import {
generateKeyPair,
@ -29,7 +31,7 @@ import {
} from '../Curve';
import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
import { ourProfileKeyService } from '../services/ourProfileKey';
import { assert } from '../util/assert';
import { assert, strictAssert } from '../util/assert';
import { getProvisioningUrl } from '../util/getProvisioningUrl';
import { SignalService as Proto } from '../protobuf';
@ -56,7 +58,7 @@ function getIdentifier(id: string | undefined) {
return parts[0];
}
type GeneratedKeysType = {
export type GeneratedKeysType = {
preKeys: Array<{
keyId: number;
publicKey: ArrayBuffer;
@ -89,16 +91,10 @@ export default class AccountManager extends EventTarget {
return this.server.requestVerificationSMS(number);
}
async encryptDeviceName(name: string, providedIdentityKey?: KeyPairType) {
async encryptDeviceName(name: string, identityKey: KeyPairType) {
if (!name) {
return null;
}
const identityKey =
providedIdentityKey ||
(await window.textsecure.storage.protocol.getIdentityKeyPair());
if (!identityKey) {
throw new Error('Identity key was not provided and is not in database!');
}
const encrypted = await window.Signal.Crypto.encryptDeviceName(
name,
identityKey.pubKey
@ -114,7 +110,10 @@ export default class AccountManager extends EventTarget {
}
async decryptDeviceName(base64: string) {
const identityKey = await window.textsecure.storage.protocol.getIdentityKeyPair();
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
const identityKey = await window.textsecure.storage.protocol.getIdentityKeyPair(
ourUuid
);
if (!identityKey) {
throw new Error('decryptDeviceName: No identity key pair!');
}
@ -144,8 +143,19 @@ export default class AccountManager extends EventTarget {
if (isNameEncrypted) {
return;
}
const deviceName = window.textsecure.storage.user.getDeviceName();
const base64 = await this.encryptDeviceName(deviceName || '');
const { storage } = window.textsecure;
const deviceName = storage.user.getDeviceName();
const identityKeyPair = await storage.protocol.getIdentityKeyPair(
storage.user.getCheckedUuid()
);
strictAssert(
identityKeyPair !== undefined,
"Can't encrypt device name without identity key pair"
);
const base64 = await this.encryptDeviceName(
deviceName || '',
identityKeyPair
);
if (base64) {
await this.server.updateDeviceName(base64);
@ -310,6 +320,7 @@ export default class AccountManager extends EventTarget {
async rotateSignedPreKey() {
return this.queueTask(async () => {
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
if (typeof signedKeyId !== 'number') {
throw new Error('Invalid signedKeyId');
@ -318,7 +329,7 @@ export default class AccountManager extends EventTarget {
const store = window.textsecure.storage.protocol;
const { server, cleanSignedPreKeys } = this;
const existingKeys = await store.loadSignedPreKeys();
const existingKeys = await store.loadSignedPreKeys(ourUuid);
existingKeys.sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
const confirmedKeys = existingKeys.filter(key => key.confirmed);
const mostRecent = confirmedKeys[0];
@ -332,7 +343,7 @@ export default class AccountManager extends EventTarget {
// eslint-disable-next-line consistent-return
return store
.getIdentityKeyPair()
.getIdentityKeyPair(ourUuid)
.then(
async (identityKey: KeyPairType | undefined) => {
if (!identityKey) {
@ -357,7 +368,7 @@ export default class AccountManager extends EventTarget {
window.log.info('Saving new signed prekey', res.keyId);
return Promise.all([
window.textsecure.storage.put('signedKeyId', signedKeyId + 1),
store.storeSignedPreKey(res.keyId, res.keyPair),
store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair),
server.setSignedPreKey({
keyId: res.keyId,
publicKey: res.keyPair.pubKey,
@ -369,7 +380,12 @@ export default class AccountManager extends EventTarget {
window.log.info('Confirming new signed prekey', res.keyId);
return Promise.all([
window.textsecure.storage.remove('signedKeyRotationRejected'),
store.storeSignedPreKey(res.keyId, res.keyPair, confirmed),
store.storeSignedPreKey(
ourUuid,
res.keyId,
res.keyPair,
confirmed
),
]);
})
.then(cleanSignedPreKeys);
@ -409,9 +425,10 @@ export default class AccountManager extends EventTarget {
}
async cleanSignedPreKeys() {
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
const store = window.textsecure.storage.protocol;
const allKeys = await store.loadSignedPreKeys();
const allKeys = await store.loadSignedPreKeys(ourUuid);
allKeys.sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
const confirmed = allKeys.filter(key => key.confirmed);
const unconfirmed = allKeys.filter(key => !key.confirmed);
@ -448,7 +465,7 @@ export default class AccountManager extends EventTarget {
window.log.info(
`Removing signed prekey: ${key.keyId} with timestamp ${timestamp}${confirmedText}`
);
await store.removeSignedPreKey(key.keyId);
await store.removeSignedPreKey(ourUuid, key.keyId);
}
})
);
@ -464,17 +481,14 @@ export default class AccountManager extends EventTarget {
readReceipts?: boolean | null,
options: { accessKey?: ArrayBuffer; uuid?: string } = {}
): Promise<void> {
const { storage } = window.textsecure;
const { accessKey, uuid } = options;
let password = btoa(utils.getString(getRandomBytes(16)));
password = password.substring(0, password.length - 2);
const registrationId = generateRegistrationId();
const previousNumber = getIdentifier(
window.textsecure.storage.get('number_id')
);
const previousUuid = getIdentifier(
window.textsecure.storage.get('uuid_id')
);
const previousNumber = getIdentifier(storage.get('number_id'));
const previousUuid = getIdentifier(storage.get('uuid_id'));
let encryptedDeviceName;
if (deviceName) {
@ -500,7 +514,10 @@ export default class AccountManager extends EventTarget {
{ accessKey, uuid }
);
const uuidChanged = previousUuid && uuid && previousUuid !== uuid;
const ourUuid = uuid || response.uuid;
strictAssert(ourUuid !== undefined, 'Should have UUID after registration');
const uuidChanged = previousUuid && ourUuid && previousUuid !== ourUuid;
// We only consider the number changed if we didn't have a UUID before
const numberChanged =
@ -519,7 +536,7 @@ export default class AccountManager extends EventTarget {
}
try {
await window.textsecure.storage.protocol.removeAllData();
await storage.protocol.removeAllData();
window.log.info('Successfully deleted previous data');
} catch (error) {
window.log.error(
@ -530,23 +547,34 @@ export default class AccountManager extends EventTarget {
}
await Promise.all([
window.textsecure.storage.remove('identityKey'),
window.textsecure.storage.user.removeCredentials(),
window.textsecure.storage.remove('registrationId'),
window.textsecure.storage.remove('regionCode'),
window.textsecure.storage.remove('userAgent'),
window.textsecure.storage.remove('profileKey'),
window.textsecure.storage.remove('read-receipt-setting'),
storage.user.removeCredentials(),
storage.remove('regionCode'),
storage.remove('userAgent'),
storage.remove('profileKey'),
storage.remove('read-receipt-setting'),
]);
if (previousUuid) {
await Promise.all([
storage.put(
'identityKeyMap',
omit(storage.get('identityKeyMap') || {}, previousUuid)
),
storage.put(
'registrationIdMap',
omit(storage.get('registrationIdMap') || {}, previousUuid)
),
]);
}
// `setCredentials` needs to be called
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
// indirectly calls `ConversationController.getConverationId()` which
// 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.
await window.textsecure.storage.user.setCredentials({
uuid,
await storage.user.setCredentials({
uuid: ourUuid,
number,
deviceId: response.deviceId ?? 1,
deviceName: deviceName ?? undefined,
@ -558,7 +586,7 @@ export default class AccountManager extends EventTarget {
// below.
const conversationId = window.ConversationController.ensureContactIds({
e164: number,
uuid,
uuid: ourUuid,
highTrust: true,
});
@ -568,36 +596,42 @@ 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 window.textsecure.storage.protocol.saveIdentityWithAttributes(
uuid || number,
{
publicKey: identityKeyPair.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: window.textsecure.storage.protocol.VerifiedStatus.VERIFIED,
nonblockingApproval: true,
}
);
await storage.protocol.saveIdentityWithAttributes(ourUuid, {
publicKey: identityKeyPair.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: storage.protocol.VerifiedStatus.VERIFIED,
nonblockingApproval: true,
});
await window.textsecure.storage.put('identityKey', identityKeyPair);
await window.textsecure.storage.put('registrationId', registrationId);
const identityKeyMap = {
...(storage.get('identityKeyMap') || {}),
[ourUuid]: {
pubKey: arrayBufferToBase64(identityKeyPair.pubKey),
privKey: arrayBufferToBase64(identityKeyPair.privKey),
},
};
const registrationIdMap = {
...(storage.get('registrationIdMap') || {}),
[ourUuid]: registrationId,
};
await storage.put('identityKeyMap', identityKeyMap);
await storage.put('registrationIdMap', registrationIdMap);
if (profileKey) {
await ourProfileKeyService.set(profileKey);
}
if (userAgent) {
await window.textsecure.storage.put('userAgent', userAgent);
await storage.put('userAgent', userAgent);
}
await window.textsecure.storage.put(
'read-receipt-setting',
Boolean(readReceipts)
);
await storage.put('read-receipt-setting', Boolean(readReceipts));
const regionCode = window.libphonenumber.util.getRegionCodeForNumber(
number
);
await window.textsecure.storage.put('regionCode', regionCode);
await window.textsecure.storage.protocol.hydrateCaches();
await storage.put('regionCode', regionCode);
await storage.protocol.hydrateCaches();
}
async clearSessionsAndPreKeys() {
@ -626,7 +660,8 @@ export default class AccountManager extends EventTarget {
}
window.log.info('confirmKeys: confirming key', key.keyId);
await store.storeSignedPreKey(key.keyId, key.keyPair, confirmed);
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
await store.storeSignedPreKey(ourUuid, key.keyId, key.keyPair, confirmed);
}
async generateKeys(count: number, providedProgressCallback?: Function) {
@ -636,6 +671,7 @@ export default class AccountManager extends EventTarget {
: null;
const startId = window.textsecure.storage.get('maxPreKeyId', 1);
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
if (typeof startId !== 'number') {
throw new Error('Invalid maxPreKeyId');
@ -645,7 +681,7 @@ export default class AccountManager extends EventTarget {
}
const store = window.textsecure.storage.protocol;
return store.getIdentityKeyPair().then(async identityKey => {
return store.getIdentityKeyPair(ourUuid).then(async identityKey => {
if (!identityKey) {
throw new Error('generateKeys: No identity key pair!');
}
@ -659,7 +695,7 @@ export default class AccountManager extends EventTarget {
for (let keyId = startId; keyId < startId + count; keyId += 1) {
promises.push(
Promise.resolve(generatePreKey(keyId)).then(async res => {
await store.storePreKey(res.keyId, res.keyPair);
await store.storePreKey(ourUuid, res.keyId, res.keyPair);
result.preKeys.push({
keyId: res.keyId,
publicKey: res.keyPair.pubKey,
@ -674,7 +710,7 @@ export default class AccountManager extends EventTarget {
promises.push(
Promise.resolve(generateSignedPreKey(identityKey, signedKeyId)).then(
async res => {
await store.storeSignedPreKey(res.keyId, res.keyPair);
await store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair);
result.signedPreKey = {
keyId: res.keyId,
publicKey: res.keyPair.pubKey,

View file

@ -0,0 +1,25 @@
// Copyright 2017-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { UUID } from '../types/UUID';
import type { SignalProtocolStore } from '../SignalProtocolStore';
export function init(signalProtocolStore: SignalProtocolStore): void {
signalProtocolStore.on(
'keychange',
async (uuid: UUID): Promise<void> => {
const conversation = await window.ConversationController.getOrCreateAndWait(
uuid.toString(),
'private'
);
conversation.addKeyChange(uuid);
const groups = await window.ConversationController.getAllGroupsInvolvingId(
conversation.id
);
for (const group of groups) {
group.addKeyChange(uuid);
}
}
);
}

View file

@ -45,6 +45,9 @@ import { parseIntOrThrow } from '../util/parseIntOrThrow';
import { Zone } from '../util/Zone';
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
import { DownloadedAttachmentType } from '../types/Attachment';
import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress';
import { UUID } from '../types/UUID';
import * as Errors from '../types/errors';
import { SignalService as Proto } from '../protobuf';
@ -754,8 +757,9 @@ export default class MessageReceiver
pendingSessions: true,
pendingUnprocessed: true,
});
const sessionStore = new Sessions({ zone });
const identityKeyStore = new IdentityKeys({ zone });
const ourUuid = this.storage.user.getCheckedUuid();
const sessionStore = new Sessions({ zone, ourUuid });
const identityKeyStore = new IdentityKeys({ zone, ourUuid });
const failed: Array<UnprocessedType> = [];
// Below we:
@ -1228,18 +1232,12 @@ export default class MessageReceiver
ciphertext: Uint8Array
): Promise<DecryptSealedSenderResult> {
const localE164 = this.storage.user.getNumber();
const localUuid = this.storage.user.getUuid();
const ourUuid = this.storage.user.getCheckedUuid();
const localDeviceId = parseIntOrThrow(
this.storage.user.getDeviceId(),
'MessageReceiver.decryptSealedSender: localDeviceId'
);
if (!localUuid) {
throw new Error(
'MessageReceiver.decryptSealedSender: Failed to fetch local UUID'
);
}
const logId = this.getEnvelopeId(envelope);
const { unsealedContent: messageContent, certificate } = envelope;
@ -1280,9 +1278,12 @@ export default class MessageReceiver
);
const sealedSenderIdentifier = certificate.senderUuid();
const sealedSenderSourceDevice = certificate.senderDeviceId();
const senderKeyStore = new SenderKeys();
const senderKeyStore = new SenderKeys({ ourUuid });
const address = `${sealedSenderIdentifier}.${sealedSenderSourceDevice}`;
const address = new QualifiedAddress(
ourUuid,
Address.create(sealedSenderIdentifier, sealedSenderSourceDevice)
);
const plaintext = await this.storage.protocol.enqueueSenderKeyJob(
address,
@ -1305,11 +1306,22 @@ export default class MessageReceiver
'unidentified message/passing to sealedSenderDecryptMessage'
);
const preKeyStore = new PreKeys();
const signedPreKeyStore = new SignedPreKeys();
const preKeyStore = new PreKeys({ ourUuid });
const signedPreKeyStore = new SignedPreKeys({ ourUuid });
const sealedSenderIdentifier = envelope.sourceUuid || envelope.source;
const address = `${sealedSenderIdentifier}.${envelope.sourceDevice}`;
const sealedSenderIdentifier = envelope.sourceUuid;
strictAssert(
sealedSenderIdentifier !== undefined,
'Empty sealed sender identifier'
);
strictAssert(
envelope.sourceDevice !== undefined,
'Empty sealed sender device'
);
const address = new QualifiedAddress(
ourUuid,
Address.create(sealedSenderIdentifier, envelope.sourceDevice)
);
const unsealedPlaintext = await this.storage.protocol.enqueueSessionJob(
address,
() =>
@ -1318,7 +1330,7 @@ export default class MessageReceiver
PublicKey.deserialize(Buffer.from(this.serverTrustRoot)),
envelope.serverTimestamp,
localE164 || null,
localUuid,
ourUuid.toString(),
localDeviceId,
sessionStore,
identityKeyStore,
@ -1341,11 +1353,20 @@ export default class MessageReceiver
const logId = this.getEnvelopeId(envelope);
const envelopeTypeEnum = Proto.Envelope.Type;
const identifier = envelope.sourceUuid || envelope.source;
const identifier = envelope.sourceUuid;
const { sourceDevice } = envelope;
const preKeyStore = new PreKeys();
const signedPreKeyStore = new SignedPreKeys();
const ourUuid = this.storage.user.getCheckedUuid();
const preKeyStore = new PreKeys({ ourUuid });
const signedPreKeyStore = new SignedPreKeys({ ourUuid });
strictAssert(identifier !== undefined, 'Empty identifier');
strictAssert(sourceDevice !== undefined, 'Empty source device');
const address = new QualifiedAddress(
ourUuid,
Address.create(identifier, sourceDevice)
);
if (envelope.type === envelopeTypeEnum.PLAINTEXT_CONTENT) {
window.log.info(`decrypt/${logId}: plaintext message`);
@ -1368,7 +1389,6 @@ export default class MessageReceiver
}
const signalMessage = SignalMessage.deserialize(Buffer.from(ciphertext));
const address = `${identifier}.${sourceDevice}`;
const plaintext = await this.storage.protocol.enqueueSessionJob(
address,
async () =>
@ -1400,7 +1420,6 @@ export default class MessageReceiver
Buffer.from(ciphertext)
);
const address = `${identifier}.${sourceDevice}`;
const plaintext = await this.storage.protocol.enqueueSessionJob(
address,
async () =>
@ -1562,7 +1581,7 @@ export default class MessageReceiver
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
const { source, sourceUuid } = envelope;
const ourE164 = this.storage.user.getNumber();
const ourUuid = this.storage.user.getUuid();
const ourUuid = this.storage.user.getCheckedUuid().toString();
const isMe =
(source && ourE164 && source === ourE164) ||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
@ -1613,7 +1632,7 @@ export default class MessageReceiver
);
let p: Promise<void> = Promise.resolve();
// eslint-disable-next-line no-bitwise
const destination = envelope.sourceUuid || envelope.source;
const destination = envelope.sourceUuid;
if (!destination) {
throw new Error(
'MessageReceiver.handleDataMessage: source and sourceUuid were falsey'
@ -1651,7 +1670,7 @@ export default class MessageReceiver
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
const { source, sourceUuid } = envelope;
const ourE164 = this.storage.user.getNumber();
const ourUuid = this.storage.user.getUuid();
const ourUuid = this.storage.user.getCheckedUuid().toString();
const isMe =
(source && ourE164 && source === ourE164) ||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
@ -1711,8 +1730,7 @@ export default class MessageReceiver
}
const { timestamp } = envelope;
const identifier =
envelope.groupId || envelope.sourceUuid || envelope.source;
const identifier = envelope.groupId || envelope.sourceUuid;
const conversation = window.ConversationController.get(identifier);
try {
@ -1848,7 +1866,7 @@ export default class MessageReceiver
// Note: we don't call removeFromCache here because this message can be combined
// with a dataMessage, for example. That processing will dictate cache removal.
const identifier = envelope.sourceUuid || envelope.source;
const identifier = envelope.sourceUuid;
const { sourceDevice } = envelope;
if (!identifier) {
throw new Error(
@ -1865,8 +1883,12 @@ export default class MessageReceiver
const senderKeyDistributionMessage = SenderKeyDistributionMessage.deserialize(
Buffer.from(distributionMessage)
);
const senderKeyStore = new SenderKeys();
const address = `${identifier}.${sourceDevice}`;
const ourUuid = this.storage.user.getCheckedUuid();
const senderKeyStore = new SenderKeys({ ourUuid });
const address = new QualifiedAddress(
ourUuid,
Address.create(identifier, sourceDevice)
);
await this.storage.protocol.enqueueSenderKeyJob(
address,
@ -2109,11 +2131,11 @@ export default class MessageReceiver
syncMessage: ProcessedSyncMessage
): Promise<void> {
const ourNumber = this.storage.user.getNumber();
const ourUuid = this.storage.user.getUuid();
const ourUuid = this.storage.user.getCheckedUuid();
const fromSelfSource = envelope.source && envelope.source === ourNumber;
const fromSelfSourceUuid =
envelope.sourceUuid && envelope.sourceUuid === ourUuid;
envelope.sourceUuid && envelope.sourceUuid === ourUuid.toString();
if (!fromSelfSource && !fromSelfSourceUuid) {
throw new Error('Received sync message from another number');
}
@ -2536,8 +2558,14 @@ export default class MessageReceiver
}
private async handleEndSession(identifier: string): Promise<void> {
const theirUuid = UUID.lookup(identifier);
if (!theirUuid) {
window.log.warn(`handleEndSession: uuid not found for ${identifier}`);
return;
}
window.log.info(`handleEndSession: closing sessions for ${identifier}`);
await this.storage.protocol.archiveAllSessions(identifier);
await this.storage.protocol.archiveAllSessions(theirUuid);
}
private async processDecrypted(

View file

@ -32,6 +32,9 @@ import {
} from './Errors';
import { CallbackResultType, CustomError } from './Types.d';
import { isValidNumber } from '../types/PhoneNumber';
import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress';
import { UUID } from '../types/UUID';
import { Sessions, IdentityKeys } from '../LibSignalStores';
import { typedArrayToArrayBuffer as toArrayBuffer } from '../Crypto';
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
@ -237,9 +240,11 @@ export default class OutgoingMessage {
recurse?: boolean
): () => Promise<void> {
return async () => {
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds(
identifier
);
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
ourUuid,
identifier,
});
if (deviceIds.length === 0) {
this.registerError(
identifier,
@ -386,9 +391,12 @@ export default class OutgoingMessage {
// We don't send to ourselves unless sealedSender is enabled
const ourNumber = window.textsecure.storage.user.getNumber();
const ourUuid = window.textsecure.storage.user.getUuid();
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
if ((identifier === ourNumber || identifier === ourUuid) && !sealedSender) {
if (
(identifier === ourNumber || identifier === ourUuid.toString()) &&
!sealedSender
) {
deviceIds = reject(
deviceIds,
deviceId =>
@ -399,18 +407,22 @@ export default class OutgoingMessage {
);
}
const sessionStore = new Sessions();
const identityKeyStore = new IdentityKeys();
const sessionStore = new Sessions({ ourUuid });
const identityKeyStore = new IdentityKeys({ ourUuid });
return Promise.all(
deviceIds.map(async destinationDeviceId => {
const address = `${identifier}.${destinationDeviceId}`;
const theirUuid = UUID.checkedLookup(identifier);
const address = new QualifiedAddress(
ourUuid,
new Address(theirUuid, destinationDeviceId)
);
return window.textsecure.storage.protocol.enqueueSessionJob<SendMetadata>(
address,
async () => {
const protocolAddress = ProtocolAddress.new(
identifier,
theirUuid.toString(),
destinationDeviceId
);
@ -566,7 +578,10 @@ export default class OutgoingMessage {
p = Promise.all(
error.response.staleDevices.map(async (deviceId: number) => {
await window.textsecure.storage.protocol.archiveSession(
`${identifier}.${deviceId}`
new QualifiedAddress(
ourUuid,
new Address(UUID.checkedLookup(identifier), deviceId)
)
);
})
);
@ -595,7 +610,7 @@ export default class OutgoingMessage {
window.log.info('closing all sessions for', identifier);
window.textsecure.storage.protocol
.archiveAllSessions(identifier)
.archiveAllSessions(UUID.checkedLookup(identifier))
.then(
() => {
throw error;
@ -623,10 +638,13 @@ export default class OutgoingMessage {
identifier: string,
deviceIdsToRemove: Array<number>
): Promise<void> {
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
const theirUuid = UUID.checkedLookup(identifier);
await Promise.all(
deviceIdsToRemove.map(async deviceId => {
await window.textsecure.storage.protocol.archiveSession(
`${identifier}.${deviceId}`
new QualifiedAddress(ourUuid, new Address(theirUuid, deviceId))
);
})
);
@ -675,9 +693,11 @@ export default class OutgoingMessage {
);
}
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds(
identifier
);
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
ourUuid,
identifier,
});
if (deviceIds.length === 0) {
await this.getKeysForIdentifier(identifier);
}

View file

@ -18,6 +18,9 @@ import {
import { assert } from '../util/assert';
import { parseIntOrThrow } from '../util/parseIntOrThrow';
import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress';
import { UUID } from '../types/UUID';
import { SenderKeys } from '../LibSignalStores';
import {
ChallengeType,
@ -737,14 +740,14 @@ export default class MessageSender {
}
const myE164 = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
const groupMembers = groupV2?.members || groupV1?.members || [];
// We should always have a UUID but have this check just in case we don't.
let isNotMe: (recipient: string) => boolean;
if (myUuid) {
isNotMe = r => r !== myE164 && r !== myUuid;
isNotMe = r => r !== myE164 && r !== myUuid.toString();
} else {
isNotMe = r => r !== myE164;
}
@ -1030,8 +1033,7 @@ export default class MessageSender {
isUpdate?: boolean;
options?: SendOptionsType;
}>): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const dataMessage = Proto.DataMessage.decode(
new FIXMEU8(encodedDataMessage)
@ -1086,7 +1088,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp,
contentHint: ContentHint.RESENDABLE,
@ -1097,8 +1099,7 @@ export default class MessageSender {
async sendRequestBlockSyncMessage(
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.BLOCKED;
@ -1110,7 +1111,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
@ -1121,8 +1122,7 @@ export default class MessageSender {
async sendRequestConfigurationSyncMessage(
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.CONFIGURATION;
@ -1134,7 +1134,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
@ -1145,8 +1145,7 @@ export default class MessageSender {
async sendRequestGroupSyncMessage(
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.GROUPS;
@ -1158,7 +1157,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
@ -1169,8 +1168,7 @@ export default class MessageSender {
async sendRequestContactSyncMessage(
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.CONTACTS;
@ -1182,7 +1180,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
@ -1193,8 +1191,7 @@ export default class MessageSender {
async sendFetchManifestSyncMessage(
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myUuid = window.textsecure.storage.user.getUuid();
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const fetchLatest = new Proto.SyncMessage.FetchLatest();
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST;
@ -1207,7 +1204,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
@ -1218,8 +1215,7 @@ export default class MessageSender {
async sendFetchLocalProfileSyncMessage(
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myUuid = window.textsecure.storage.user.getUuid();
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const fetchLatest = new Proto.SyncMessage.FetchLatest();
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.LOCAL_PROFILE;
@ -1232,7 +1228,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
@ -1243,8 +1239,7 @@ export default class MessageSender {
async sendRequestKeySyncMessage(
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myUuid = window.textsecure.storage.user.getUuid();
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const request = new Proto.SyncMessage.Request();
request.type = Proto.SyncMessage.Request.Type.KEYS;
@ -1257,7 +1252,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
@ -1273,8 +1268,7 @@ export default class MessageSender {
}>,
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const syncMessage = this.createSyncMessage();
syncMessage.read = [];
@ -1289,7 +1283,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.RESENDABLE,
@ -1305,8 +1299,7 @@ export default class MessageSender {
}>,
options?: SendOptionsType
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const syncMessage = this.createSyncMessage();
syncMessage.viewed = views.map(view => new Proto.SyncMessage.Viewed(view));
@ -1316,7 +1309,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.RESENDABLE,
@ -1330,8 +1323,7 @@ export default class MessageSender {
timestamp: number,
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const syncMessage = this.createSyncMessage();
@ -1349,7 +1341,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.RESENDABLE,
@ -1366,8 +1358,7 @@ export default class MessageSender {
}>,
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const syncMessage = this.createSyncMessage();
@ -1390,7 +1381,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.RESENDABLE,
@ -1406,8 +1397,7 @@ export default class MessageSender {
}>,
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const ENUM = Proto.SyncMessage.StickerPackOperation.Type;
const packOperations = operations.map(item => {
@ -1430,7 +1420,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: contentMessage,
timestamp: Date.now(),
contentHint: ContentHint.IMPLICIT,
@ -1445,8 +1435,7 @@ export default class MessageSender {
identityKey: Readonly<ArrayBuffer>,
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getCheckedUuid();
const now = Date.now();
if (!destinationE164 && !destinationUuid) {
@ -1488,7 +1477,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
return this.sendIndividualProto({
identifier: myUuid || myNumber,
identifier: myUuid.toString(),
proto: secondMessage,
timestamp: now,
contentHint: ContentHint.RESENDABLE,
@ -1675,6 +1664,7 @@ export default class MessageSender {
proto.timestamp = timestamp;
const identifier = uuid || e164;
const theirUuid = UUID.checkedLookup(identifier);
const logError = (prefix: string) => (error: Error) => {
window.log.error(prefix, error && error.stack ? error.stack : error);
@ -1684,7 +1674,7 @@ export default class MessageSender {
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
const sendToContactPromise = window.textsecure.storage.protocol
.archiveAllSessions(identifier)
.archiveAllSessions(theirUuid)
.catch(logError('resetSession/archiveAllSessions1 error:'))
.then(async () => {
window.log.info(
@ -1706,14 +1696,14 @@ export default class MessageSender {
})
.then(async result => {
await window.textsecure.storage.protocol
.archiveAllSessions(identifier)
.archiveAllSessions(theirUuid)
.catch(logError('resetSession/archiveAllSessions2 error:'));
return result;
});
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
// We already sent the reset session to our other devices in the code above!
if ((e164 && e164 === myNumber) || (uuid && uuid === myUuid)) {
return sendToContactPromise;
@ -1882,7 +1872,7 @@ export default class MessageSender {
: undefined;
const myE164 = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
const identifiers = recipients.filter(id => id !== myE164 && id !== myUuid);
if (identifiers.length === 0) {
@ -1921,20 +1911,21 @@ export default class MessageSender {
async getSenderKeyDistributionMessage(
distributionId: string
): Promise<SenderKeyDistributionMessage> {
const ourUuid = window.textsecure.storage.user.getUuid();
if (!ourUuid) {
throw new Error(
'getSenderKeyDistributionMessage: Failed to fetch our UUID!'
);
}
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
const ourDeviceId = parseIntOrThrow(
window.textsecure.storage.user.getDeviceId(),
'getSenderKeyDistributionMessage'
);
const protocolAddress = ProtocolAddress.new(ourUuid, ourDeviceId);
const address = `${ourUuid}.${ourDeviceId}`;
const senderKeyStore = new SenderKeys();
const protocolAddress = ProtocolAddress.new(
ourUuid.toString(),
ourDeviceId
);
const address = new QualifiedAddress(
ourUuid,
new Address(ourUuid, ourDeviceId)
);
const senderKeyStore = new SenderKeys({ ourUuid });
return window.textsecure.storage.protocol.enqueueSenderKeyJob(
address,
@ -2044,7 +2035,7 @@ export default class MessageSender {
options?: Readonly<SendOptionsType>
): Promise<CallbackResultType> {
const myNumber = window.textsecure.storage.user.getNumber();
const myUuid = window.textsecure.storage.user.getUuid();
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
const recipients = groupIdentifiers.filter(
identifier => identifier !== myNumber && identifier !== myUuid
);

View file

@ -6,9 +6,14 @@ import type { IncomingWebSocketRequest } from './WebsocketResources';
export {
IdentityKeyType,
IdentityKeyIdType,
PreKeyIdType,
PreKeyType,
SenderKeyIdType,
SenderKeyType,
SessionIdType,
SessionType,
SignedPreKeyIdType,
SignedPreKeyType,
UnprocessedType,
UnprocessedUpdateType,

View file

@ -10,6 +10,9 @@ import {
import { UnregisteredUserError } from './Errors';
import { Sessions, IdentityKeys } from '../LibSignalStores';
import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress';
import { UUID } from '../types/UUID';
import { ServerKeysType, WebAPIType } from './WebAPI';
export async function getKeysForIdentifier(
@ -32,7 +35,11 @@ export async function getKeysForIdentifier(
};
} catch (error) {
if (error.name === 'HTTPError' && error.code === 404) {
await window.textsecure.storage.protocol.archiveAllSessions(identifier);
const theirUuid = UUID.lookup(identifier);
if (theirUuid) {
await window.textsecure.storage.protocol.archiveAllSessions(theirUuid);
}
}
throw new UnregisteredUserError(identifier, error);
}
@ -72,8 +79,9 @@ async function handleServerKeys(
response: ServerKeysType,
devicesToUpdate?: Array<number>
): Promise<void> {
const sessionStore = new Sessions();
const identityKeyStore = new IdentityKeys();
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
const sessionStore = new Sessions({ ourUuid });
const identityKeyStore = new IdentityKeys({ ourUuid });
await Promise.all(
response.devices.map(async device => {
@ -95,7 +103,11 @@ async function handleServerKeys(
`getKeysForIdentifier/${identifier}: Missing signed prekey for deviceId ${deviceId}`
);
}
const protocolAddress = ProtocolAddress.new(identifier, deviceId);
const theirUuid = UUID.checkedLookup(identifier);
const protocolAddress = ProtocolAddress.new(
theirUuid.toString(),
deviceId
);
const preKeyId = preKey?.keyId || null;
const preKeyObject = preKey
? PublicKey.deserialize(Buffer.from(preKey.publicKey))
@ -118,7 +130,10 @@ async function handleServerKeys(
identityKey
);
const address = `${identifier}.${deviceId}`;
const address = new QualifiedAddress(
ourUuid,
new Address(theirUuid, deviceId)
);
await window.textsecure.storage.protocol
.enqueueSessionJob(address, () =>
processPreKeyBundle(

View file

@ -5,6 +5,7 @@ import { WebAPICredentials } from '../Types.d';
import { strictAssert } from '../../util/assert';
import { StorageInterface } from '../../types/Storage.d';
import { UUID } from '../../types/UUID';
import Helpers from '../Helpers';
@ -56,10 +57,16 @@ export class User {
return Helpers.unencodeNumber(numberId)[0];
}
public getUuid(): string | undefined {
public getUuid(): UUID | undefined {
const uuid = this.storage.get('uuid_id');
if (uuid === undefined) return undefined;
return Helpers.unencodeNumber(uuid.toLowerCase())[0];
return new UUID(Helpers.unencodeNumber(uuid.toLowerCase())[0]);
}
public getCheckedUuid(): UUID {
const uuid = this.getUuid();
strictAssert(uuid !== undefined, 'Must have our own uuid');
return uuid;
}
public getDeviceId(): number | undefined {