diff --git a/background.html b/background.html
index cab672b479..cbeb81d99f 100644
--- a/background.html
+++ b/background.html
@@ -309,7 +309,6 @@
type="text/javascript"
src="js/rotate_signed_prekey_listener.js"
>
-
diff --git a/js/keychange_listener.js b/js/keychange_listener.js
deleted file mode 100644
index 84053e6f95..0000000000
--- a/js/keychange_listener.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2017-2020 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-/* global Whisper, SignalProtocolStore, ConversationController, _ */
-
-/* eslint-disable more/no-then */
-
-// eslint-disable-next-line func-names
-(function () {
- window.Whisper = window.Whisper || {};
-
- Whisper.KeyChangeListener = {
- init(signalProtocolStore) {
- if (!(signalProtocolStore instanceof SignalProtocolStore)) {
- throw new Error('KeyChangeListener requires a SignalProtocolStore');
- }
-
- signalProtocolStore.on('keychange', async identifier => {
- const conversation = await ConversationController.getOrCreateAndWait(
- identifier,
- 'private'
- );
- conversation.addKeyChange(identifier);
-
- const groups = await ConversationController.getAllGroupsInvolvingId(
- conversation.id
- );
- _.forEach(groups, group => {
- group.addKeyChange(identifier);
- });
- });
- },
- };
-})();
diff --git a/js/modules/signal.js b/js/modules/signal.js
index a08452f835..5ae22b8b16 100644
--- a/js/modules/signal.js
+++ b/js/modules/signal.js
@@ -142,6 +142,9 @@ const Errors = require('../../ts/types/errors');
const MessageType = require('./types/message');
const MIME = require('../../ts/types/MIME');
const SettingsType = require('../../ts/types/Settings');
+const { UUID } = require('../../ts/types/UUID');
+const { Address } = require('../../ts/types/Address');
+const { QualifiedAddress } = require('../../ts/types/QualifiedAddress');
// Views
const Initialization = require('./views/initialization');
@@ -430,6 +433,9 @@ exports.setup = (options = {}) => {
MIME,
Settings: SettingsType,
VisualAttachment,
+ UUID,
+ Address,
+ QualifiedAddress,
};
const Views = {
diff --git a/libtextsecure/test/generate_keys_test.js b/libtextsecure/test/generate_keys_test.js
deleted file mode 100644
index 6ef771018c..0000000000
--- a/libtextsecure/test/generate_keys_test.js
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2015-2020 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-/* global textsecure */
-
-describe('Key generation', function thisNeeded() {
- const count = 10;
- this.timeout(count * 2000);
-
- function validateStoredKeyPair(keyPair) {
- /* Ensure the keypair matches the format used internally by libsignal-protocol */
- assert.isObject(keyPair, 'Stored keyPair is not an object');
- assert.instanceOf(keyPair.pubKey, ArrayBuffer);
- assert.instanceOf(keyPair.privKey, ArrayBuffer);
- assert.strictEqual(keyPair.pubKey.byteLength, 33);
- assert.strictEqual(new Uint8Array(keyPair.pubKey)[0], 5);
- assert.strictEqual(keyPair.privKey.byteLength, 32);
- }
- function itStoresPreKey(keyId) {
- it(`prekey ${keyId} is valid`, () =>
- textsecure.storage.protocol.loadPreKey(keyId).then(keyPair => {
- validateStoredKeyPair(keyPair);
- }));
- }
- function itStoresSignedPreKey(keyId) {
- it(`signed prekey ${keyId} is valid`, () =>
- textsecure.storage.protocol.loadSignedPreKey(keyId).then(keyPair => {
- validateStoredKeyPair(keyPair);
- }));
- }
- function validateResultKey(resultKey) {
- return textsecure.storage.protocol
- .loadPreKey(resultKey.keyId)
- .then(keyPair => {
- assertEqualArrayBuffers(resultKey.publicKey, keyPair.pubKey);
- });
- }
- function validateResultSignedKey(resultSignedKey) {
- return textsecure.storage.protocol
- .loadSignedPreKey(resultSignedKey.keyId)
- .then(keyPair => {
- assertEqualArrayBuffers(resultSignedKey.publicKey, keyPair.pubKey);
- });
- }
-
- before(async () => {
- localStorage.clear();
- const keyPair = window.Signal.Curve.generateKeyPair();
- await textsecure.storage.protocol.put('identityKey', keyPair);
- });
-
- describe('the first time', () => {
- let result;
- /* result should have this format
- * {
- * preKeys: [ { keyId, publicKey }, ... ],
- * signedPreKey: { keyId, publicKey, signature },
- * identityKey:
- * }
- */
- before(() => {
- const accountManager = new textsecure.AccountManager('');
- return accountManager.generateKeys(count).then(res => {
- result = res;
- });
- });
- for (let i = 1; i <= count; i += 1) {
- itStoresPreKey(i);
- }
- itStoresSignedPreKey(1);
-
- it(`result contains ${count} preKeys`, () => {
- assert.isArray(result.preKeys);
- assert.lengthOf(result.preKeys, count);
- for (let i = 0; i < count; i += 1) {
- assert.isObject(result.preKeys[i]);
- }
- });
- it('result contains the correct keyIds', () => {
- for (let i = 0; i < count; i += 1) {
- assert.strictEqual(result.preKeys[i].keyId, i + 1);
- }
- });
- it('result contains the correct public keys', () =>
- Promise.all(result.preKeys.map(validateResultKey)));
- it('returns a signed prekey', () => {
- assert.strictEqual(result.signedPreKey.keyId, 1);
- assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
- return validateResultSignedKey(result.signedPreKey);
- });
- });
- describe('the second time', () => {
- let result;
- before(() => {
- const accountManager = new textsecure.AccountManager('');
- return accountManager.generateKeys(count).then(res => {
- result = res;
- });
- });
- for (let i = 1; i <= 2 * count; i += 1) {
- itStoresPreKey(i);
- }
- itStoresSignedPreKey(1);
- itStoresSignedPreKey(2);
- it(`result contains ${count} preKeys`, () => {
- assert.isArray(result.preKeys);
- assert.lengthOf(result.preKeys, count);
- for (let i = 0; i < count; i += 1) {
- assert.isObject(result.preKeys[i]);
- }
- });
- it('result contains the correct keyIds', () => {
- for (let i = 1; i <= count; i += 1) {
- assert.strictEqual(result.preKeys[i - 1].keyId, i + count);
- }
- });
- it('result contains the correct public keys', () =>
- Promise.all(result.preKeys.map(validateResultKey)));
- it('returns a signed prekey', () => {
- assert.strictEqual(result.signedPreKey.keyId, 2);
- assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
- return validateResultSignedKey(result.signedPreKey);
- });
- });
- describe('the third time', () => {
- let result;
- before(() => {
- const accountManager = new textsecure.AccountManager('');
- return accountManager.generateKeys(count).then(res => {
- result = res;
- });
- });
- for (let i = 1; i <= 3 * count; i += 1) {
- itStoresPreKey(i);
- }
- itStoresSignedPreKey(2);
- itStoresSignedPreKey(3);
- it(`result contains ${count} preKeys`, () => {
- assert.isArray(result.preKeys);
- assert.lengthOf(result.preKeys, count);
- for (let i = 0; i < count; i += 1) {
- assert.isObject(result.preKeys[i]);
- }
- });
- it('result contains the correct keyIds', () => {
- for (let i = 1; i <= count; i += 1) {
- assert.strictEqual(result.preKeys[i - 1].keyId, i + 2 * count);
- }
- });
- it('result contains the correct public keys', () =>
- Promise.all(result.preKeys.map(validateResultKey)));
- it('result contains a signed prekey', () => {
- assert.strictEqual(result.signedPreKey.keyId, 3);
- assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
- return validateResultSignedKey(result.signedPreKey);
- });
- });
-});
diff --git a/libtextsecure/test/in_memory_signal_protocol_store.js b/libtextsecure/test/in_memory_signal_protocol_store.js
deleted file mode 100644
index e9b4ae5689..0000000000
--- a/libtextsecure/test/in_memory_signal_protocol_store.js
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2016-2020 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-function SignalProtocolStore() {
- this.store = {};
-}
-
-SignalProtocolStore.prototype = {
- VerifiedStatus: {
- DEFAULT: 0,
- VERIFIED: 1,
- UNVERIFIED: 2,
- },
-
- getIdentityKeyPair() {
- return Promise.resolve(this.get('identityKey'));
- },
- getLocalRegistrationId() {
- return Promise.resolve(this.get('registrationId'));
- },
- put(key, value) {
- if (
- key === undefined ||
- value === undefined ||
- key === null ||
- value === null
- ) {
- throw new Error('Tried to store undefined/null');
- }
- this.store[key] = value;
- },
- get(key, defaultValue) {
- if (key === null || key === undefined) {
- throw new Error('Tried to get value for undefined/null key');
- }
- if (key in this.store) {
- return this.store[key];
- }
- return defaultValue;
- },
- remove(key) {
- if (key === null || key === undefined) {
- throw new Error('Tried to remove value for undefined/null key');
- }
- delete this.store[key];
- },
-
- isTrustedIdentity(identifier, identityKey) {
- if (identifier === null || identifier === undefined) {
- throw new Error('tried to check identity key for undefined/null key');
- }
- if (!(identityKey instanceof ArrayBuffer)) {
- throw new Error('Expected identityKey to be an ArrayBuffer');
- }
- const trusted = this.get(`identityKey${identifier}`);
- if (trusted === undefined) {
- return Promise.resolve(true);
- }
- return Promise.resolve(identityKey === trusted);
- },
- loadIdentityKey(identifier) {
- if (identifier === null || identifier === undefined) {
- throw new Error('Tried to get identity key for undefined/null key');
- }
- return new Promise(resolve => {
- resolve(this.get(`identityKey${identifier}`));
- });
- },
- saveIdentity(identifier, identityKey) {
- if (identifier === null || identifier === undefined) {
- throw new Error('Tried to put identity key for undefined/null key');
- }
- return new Promise(resolve => {
- const existing = this.get(`identityKey${identifier}`);
- this.put(`identityKey${identifier}`, identityKey);
- if (existing && existing !== identityKey) {
- resolve(true);
- } else {
- resolve(false);
- }
- });
- },
-
- /* Returns a prekeypair object or undefined */
- loadPreKey(keyId) {
- return new Promise(resolve => {
- const res = this.get(`25519KeypreKey${keyId}`);
- resolve(res);
- });
- },
- storePreKey(keyId, keyPair) {
- return new Promise(resolve => {
- resolve(this.put(`25519KeypreKey${keyId}`, keyPair));
- });
- },
- removePreKey(keyId) {
- return new Promise(resolve => {
- resolve(this.remove(`25519KeypreKey${keyId}`));
- });
- },
-
- /* Returns a signed keypair object or undefined */
- loadSignedPreKey(keyId) {
- return new Promise(resolve => {
- const res = this.get(`25519KeysignedKey${keyId}`);
- resolve(res);
- });
- },
- loadSignedPreKeys() {
- return new Promise(resolve => {
- const res = [];
- const keys = Object.keys(this.store);
- for (let i = 0, max = keys.length; i < max; i += 1) {
- const key = keys[i];
- if (key.startsWith('25519KeysignedKey')) {
- res.push(this.store[key]);
- }
- }
- resolve(res);
- });
- },
- storeSignedPreKey(keyId, keyPair) {
- return new Promise(resolve => {
- resolve(this.put(`25519KeysignedKey${keyId}`, keyPair));
- });
- },
- removeSignedPreKey(keyId) {
- return new Promise(resolve => {
- resolve(this.remove(`25519KeysignedKey${keyId}`));
- });
- },
-
- loadSession(identifier) {
- return new Promise(resolve => {
- resolve(this.get(`session${identifier}`));
- });
- },
- storeSession(identifier, record) {
- return new Promise(resolve => {
- resolve(this.put(`session${identifier}`, record));
- });
- },
- removeAllSessions(identifier) {
- return new Promise(resolve => {
- const keys = Object.keys(this.store);
- for (let i = 0, max = keys.length; i < max; i += 1) {
- const key = keys[i];
- if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
- delete this.store[key];
- }
- }
- resolve();
- });
- },
- getDeviceIds(identifier) {
- return new Promise(resolve => {
- const deviceIds = [];
- const keys = Object.keys(this.store);
- for (let i = 0, max = keys.length; i < max; i += 1) {
- const key = keys[i];
- if (key.match(RegExp(`^session${identifier.replace('+', '\\+')}.+`))) {
- deviceIds.push(parseInt(key.split('.')[1], 10));
- }
- }
- resolve(deviceIds);
- });
- },
-
- getUnprocessedCount: () => Promise.resolve(0),
- getAllUnprocessed: () => Promise.resolve([]),
- getUnprocessedById: () => Promise.resolve(null),
- addUnprocessed: () => Promise.resolve(),
- addMultipleUnprocessed: () => Promise.resolve(),
- updateUnprocessedAttempts: () => Promise.resolve(),
- updateUnprocessedWithData: () => Promise.resolve(),
- updateUnprocessedsWithData: () => Promise.resolve(),
- removeUnprocessed: () => Promise.resolve(),
- removeAllUnprocessed: () => Promise.resolve(),
-};
diff --git a/libtextsecure/test/index.html b/libtextsecure/test/index.html
index c0666ec67c..4e4decc364 100644
--- a/libtextsecure/test/index.html
+++ b/libtextsecure/test/index.html
@@ -14,10 +14,6 @@
-
-
diff --git a/libtextsecure/test/storage_test.js b/libtextsecure/test/storage_test.js
deleted file mode 100644
index 09882403d0..0000000000
--- a/libtextsecure/test/storage_test.js
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright 2015-2020 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-/* global textsecure, storage, ConversationController */
-
-describe('SignalProtocolStore', () => {
- const store = textsecure.storage.protocol;
- const identifier = '+5558675309';
- const identityKey = {
- pubKey: window.Signal.Crypto.getRandomBytes(33),
- privKey: window.Signal.Crypto.getRandomBytes(32),
- };
- const testKey = {
- pubKey: window.Signal.Crypto.getRandomBytes(33),
- privKey: window.Signal.Crypto.getRandomBytes(32),
- };
- before(async () => {
- localStorage.clear();
- ConversationController.reset();
- // store.hydrateCaches();
- await storage.fetch();
- await ConversationController.load();
- await ConversationController.getOrCreateAndWait(identifier, 'private');
- });
- it('retrieves my registration id', async () => {
- store.put('registrationId', 1337);
-
- const reg = await store.getLocalRegistrationId();
- assert.strictEqual(reg, 1337);
- });
- it('retrieves my identity key', async () => {
- store.put('identityKey', identityKey);
- const key = await store.getIdentityKeyPair();
- assertEqualArrayBuffers(key.pubKey, identityKey.pubKey);
- assertEqualArrayBuffers(key.privKey, identityKey.privKey);
- });
- it('stores identity keys', async () => {
- await store.saveIdentity(identifier, testKey.pubKey);
- const key = await store.loadIdentityKey(identifier);
- assertEqualArrayBuffers(key, testKey.pubKey);
- });
- it('returns whether a key is trusted', async () => {
- const newIdentity = window.Signal.Crypto.getRandomBytes(33);
- await store.saveIdentity(identifier, testKey.pubKey);
-
- const trusted = await store.isTrustedIdentity(identifier, newIdentity);
- if (trusted) {
- throw new Error('Allowed to overwrite identity key');
- }
- });
- it('returns whether a key is untrusted', async () => {
- await store.saveIdentity(identifier, testKey.pubKey);
- const trusted = await store.isTrustedIdentity(identifier, testKey.pubKey);
-
- if (!trusted) {
- throw new Error('Allowed to overwrite identity key');
- }
- });
- it('stores prekeys', async () => {
- await store.storePreKey(1, testKey);
-
- const key = await store.loadPreKey(1);
- assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
- assertEqualArrayBuffers(key.privKey, testKey.privKey);
- });
- it('deletes prekeys', async () => {
- await store.storePreKey(2, testKey);
- await store.removePreKey(2, testKey);
-
- const key = await store.loadPreKey(2);
- assert.isUndefined(key);
- });
- it('stores signed prekeys', async () => {
- await store.storeSignedPreKey(3, testKey);
-
- const key = await store.loadSignedPreKey(3);
- assertEqualArrayBuffers(key.pubKey, testKey.pubKey);
- assertEqualArrayBuffers(key.privKey, testKey.privKey);
- });
- it('deletes signed prekeys', async () => {
- await store.storeSignedPreKey(4, testKey);
- await store.removeSignedPreKey(4, testKey);
-
- const key = await store.loadSignedPreKey(4);
- assert.isUndefined(key);
- });
- it('stores sessions', async () => {
- const testRecord = 'an opaque string';
- const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
-
- await Promise.all(
- devices.map(async encodedNumber => {
- await store.storeSession(encodedNumber, testRecord + encodedNumber);
- })
- );
-
- const records = await Promise.all(
- devices.map(store.loadSession.bind(store))
- );
-
- for (let i = 0, max = records.length; i < max; i += 1) {
- assert.strictEqual(records[i], testRecord + devices[i]);
- }
- });
- it('removes all sessions for a number', async () => {
- const testRecord = 'an opaque string';
- const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
-
- await Promise.all(
- devices.map(async encodedNumber => {
- await store.storeSession(encodedNumber, testRecord + encodedNumber);
- })
- );
-
- await store.removeAllSessions(identifier);
-
- const records = await Promise.all(
- devices.map(store.loadSession.bind(store))
- );
-
- for (let i = 0, max = records.length; i < max; i += 1) {
- assert.isUndefined(records[i]);
- }
- });
- it('returns deviceIds for a number', async () => {
- const testRecord = 'an opaque string';
- const devices = [1, 2, 3].map(deviceId => [identifier, deviceId].join('.'));
-
- await Promise.all(
- devices.map(async encodedNumber => {
- await store.storeSession(encodedNumber, testRecord + encodedNumber);
- })
- );
-
- const deviceIds = await store.getDeviceIds(identifier);
- assert.sameMembers(deviceIds, [1, 2, 3]);
- });
- it('returns empty array for a number with no device ids', async () => {
- const deviceIds = await store.getDeviceIds('foo');
- assert.sameMembers(deviceIds, []);
- });
-});
diff --git a/preload.js b/preload.js
index f457c648f6..55819a7e41 100644
--- a/preload.js
+++ b/preload.js
@@ -189,6 +189,8 @@ try {
statistics = {};
}
+ const ourUuid = window.textsecure.storage.user.getUuid();
+
event.sender.send('additional-log-data-response', {
capabilities: ourCapabilities || {},
remoteConfig: _.mapValues(remoteConfig, ({ value, enabled }) => {
@@ -200,7 +202,7 @@ try {
user: {
deviceId: window.textsecure.storage.user.getDeviceId(),
e164: window.textsecure.storage.user.getNumber(),
- uuid: window.textsecure.storage.user.getUuid(),
+ uuid: ourUuid && ourUuid.toString(),
conversationId: ourConversation && ourConversation.id,
},
});
diff --git a/test/index.html b/test/index.html
index ec39862435..6578502492 100644
--- a/test/index.html
+++ b/test/index.html
@@ -248,11 +248,6 @@
>
-
-
diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts
index f2642fce11..2909c5cabd 100644
--- a/ts/ConversationController.ts
+++ b/ts/ConversationController.ts
@@ -7,6 +7,7 @@ import PQueue from 'p-queue';
import dataInterface from './sql/Client';
import {
ConversationModelCollectionType,
+ ConversationAttributesType,
ConversationAttributesTypeType,
} from './model-types.d';
import { ConversationModel } from './models/conversations';
@@ -15,6 +16,9 @@ import { assert } from './util/assert';
import { isValidGuid } from './util/isValidGuid';
import { map, reduce } from './util/iterables';
import { isGroupV1, isGroupV2 } from './util/whatTypeOfConversation';
+import { UUID } from './types/UUID';
+import { Address } from './types/Address';
+import { QualifiedAddress } from './types/QualifiedAddress';
const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
@@ -156,7 +160,7 @@ export class ConversationController {
}
dangerouslyCreateAndAdd(
- attributes: Partial
+ attributes: Partial
): ConversationModel {
return this._conversations.add(attributes);
}
@@ -295,7 +299,7 @@ export class ConversationController {
getOurConversationId(): string | undefined {
const e164 = window.textsecure.storage.user.getNumber();
- const uuid = window.textsecure.storage.user.getUuid();
+ const uuid = window.textsecure.storage.user.getUuid()?.toString();
return this.ensureContactIds({ e164, uuid, highTrust: true });
}
@@ -639,13 +643,14 @@ export class ConversationController {
}
const obsoleteId = obsolete.get('id');
+ const obsoleteUuid = obsolete.get('uuid');
const currentId = current.get('id');
window.log.warn('combineConversations: Combining two conversations', {
obsolete: obsoleteId,
current: currentId,
});
- if (conversationType === 'private') {
+ if (conversationType === 'private' && obsoleteUuid) {
if (!current.get('profileKey') && obsolete.get('profileKey')) {
window.log.warn(
'combineConversations: Copying profile key from old to new contact'
@@ -661,21 +666,30 @@ export class ConversationController {
window.log.warn(
'combineConversations: Delete all sessions tied to old conversationId'
);
- const deviceIds = await window.textsecure.storage.protocol.getDeviceIds(
- obsoleteId
- );
+ const ourUuid = window.textsecure.storage.user.getCheckedUuid();
+ const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
+ ourUuid,
+ identifier: obsoleteUuid,
+ });
await Promise.all(
deviceIds.map(async deviceId => {
- await window.textsecure.storage.protocol.removeSession(
- `${obsoleteId}.${deviceId}`
+ const addr = new QualifiedAddress(
+ ourUuid,
+ Address.create(obsoleteUuid, deviceId)
);
+ await window.textsecure.storage.protocol.removeSession(addr);
})
);
window.log.warn(
'combineConversations: Delete all identity information tied to old conversationId'
);
- await window.textsecure.storage.protocol.removeIdentityKey(obsoleteId);
+
+ if (obsoleteUuid) {
+ await window.textsecure.storage.protocol.removeIdentityKey(
+ new UUID(obsoleteUuid)
+ );
+ }
window.log.warn(
'combineConversations: Ensure that all V1 groups have new conversationId instead of old'
diff --git a/ts/LibSignalStores.ts b/ts/LibSignalStores.ts
index d69e767b2b..e21afc5b44 100644
--- a/ts/LibSignalStores.ts
+++ b/ts/LibSignalStores.ts
@@ -23,28 +23,42 @@ import {
Uuid,
} from '@signalapp/signal-client';
import { freezePreKey, freezeSignedPreKey } from './SignalProtocolStore';
+import { Address } from './types/Address';
+import { QualifiedAddress } from './types/QualifiedAddress';
+import type { UUID } from './types/UUID';
import { typedArrayToArrayBuffer } from './Crypto';
import { Zone } from './util/Zone';
-function encodedNameFromAddress(address: ProtocolAddress): string {
+function encodeAddress(address: ProtocolAddress): Address {
const name = address.name();
const deviceId = address.deviceId();
- const encodedName = `${name}.${deviceId}`;
- return encodedName;
+ return Address.create(name, deviceId);
}
-export type SessionsOptions = {
- readonly zone?: Zone;
-};
+function toQualifiedAddress(
+ ourUuid: UUID,
+ address: ProtocolAddress
+): QualifiedAddress {
+ return new QualifiedAddress(ourUuid, encodeAddress(address));
+}
+
+export type SessionsOptions = Readonly<{
+ ourUuid: UUID;
+ zone?: Zone;
+}>;
export class Sessions extends SessionStore {
+ private readonly ourUuid: UUID;
+
private readonly zone: Zone | undefined;
- constructor(options: SessionsOptions = {}) {
+ constructor({ ourUuid, zone }: SessionsOptions) {
super();
- this.zone = options.zone;
+
+ this.ourUuid = ourUuid;
+ this.zone = zone;
}
async saveSession(
@@ -52,16 +66,16 @@ export class Sessions extends SessionStore {
record: SessionRecord
): Promise {
await window.textsecure.storage.protocol.storeSession(
- encodedNameFromAddress(address),
+ toQualifiedAddress(this.ourUuid, address),
record,
{ zone: this.zone }
);
}
async getSession(name: ProtocolAddress): Promise {
- const encodedName = encodedNameFromAddress(name);
+ const encodedAddress = toQualifiedAddress(this.ourUuid, name);
const record = await window.textsecure.storage.protocol.loadSession(
- encodedName,
+ encodedAddress,
{ zone: this.zone }
);
@@ -71,27 +85,36 @@ export class Sessions extends SessionStore {
async getExistingSessions(
addresses: Array
): Promise> {
- const encodedAddresses = addresses.map(encodedNameFromAddress);
+ const encodedAddresses = addresses.map(addr =>
+ toQualifiedAddress(this.ourUuid, addr)
+ );
return window.textsecure.storage.protocol.loadSessions(encodedAddresses, {
zone: this.zone,
});
}
}
-export type IdentityKeysOptions = {
- readonly zone?: Zone;
-};
+export type IdentityKeysOptions = Readonly<{
+ ourUuid: UUID;
+ zone?: Zone;
+}>;
export class IdentityKeys extends IdentityKeyStore {
+ private readonly ourUuid: UUID;
+
private readonly zone: Zone | undefined;
- constructor({ zone }: IdentityKeysOptions = {}) {
+ constructor({ ourUuid, zone }: IdentityKeysOptions) {
super();
+
+ this.ourUuid = ourUuid;
this.zone = zone;
}
async getIdentityKey(): Promise {
- const keyPair = await window.textsecure.storage.protocol.getIdentityKeyPair();
+ const keyPair = await window.textsecure.storage.protocol.getIdentityKeyPair(
+ this.ourUuid
+ );
if (!keyPair) {
throw new Error('IdentityKeyStore/getIdentityKey: No identity key!');
}
@@ -99,7 +122,9 @@ export class IdentityKeys extends IdentityKeyStore {
}
async getLocalRegistrationId(): Promise {
- const id = await window.textsecure.storage.protocol.getLocalRegistrationId();
+ const id = await window.textsecure.storage.protocol.getLocalRegistrationId(
+ this.ourUuid
+ );
if (!isNumber(id)) {
throw new Error(
'IdentityKeyStore/getLocalRegistrationId: No registration id!'
@@ -109,9 +134,9 @@ export class IdentityKeys extends IdentityKeyStore {
}
async getIdentity(address: ProtocolAddress): Promise {
- const encodedName = encodedNameFromAddress(address);
+ const encodedAddress = encodeAddress(address);
const key = await window.textsecure.storage.protocol.loadIdentityKey(
- encodedName
+ encodedAddress.uuid
);
if (!key) {
@@ -122,13 +147,13 @@ export class IdentityKeys extends IdentityKeyStore {
}
async saveIdentity(name: ProtocolAddress, key: PublicKey): Promise {
- const encodedName = encodedNameFromAddress(name);
+ const encodedAddress = encodeAddress(name);
const publicKey = typedArrayToArrayBuffer(key.serialize());
// Pass `zone` to let `saveIdentity` archive sibling sessions when identity
// key changes.
return window.textsecure.storage.protocol.saveIdentity(
- encodedName,
+ encodedAddress,
publicKey,
false,
{ zone: this.zone }
@@ -140,27 +165,42 @@ export class IdentityKeys extends IdentityKeyStore {
key: PublicKey,
direction: Direction
): Promise {
- const encodedName = encodedNameFromAddress(name);
+ const encodedAddress = encodeAddress(name);
const publicKey = typedArrayToArrayBuffer(key.serialize());
return window.textsecure.storage.protocol.isTrustedIdentity(
- encodedName,
+ encodedAddress,
publicKey,
direction
);
}
}
+export type PreKeysOptions = Readonly<{
+ ourUuid: UUID;
+}>;
+
export class PreKeys extends PreKeyStore {
+ private readonly ourUuid: UUID;
+
+ constructor({ ourUuid }: PreKeysOptions) {
+ super();
+ this.ourUuid = ourUuid;
+ }
+
async savePreKey(id: number, record: PreKeyRecord): Promise {
await window.textsecure.storage.protocol.storePreKey(
+ this.ourUuid,
id,
freezePreKey(record)
);
}
async getPreKey(id: number): Promise {
- const preKey = await window.textsecure.storage.protocol.loadPreKey(id);
+ const preKey = await window.textsecure.storage.protocol.loadPreKey(
+ this.ourUuid,
+ id
+ );
if (preKey === undefined) {
throw new Error(`getPreKey: PreKey ${id} not found`);
@@ -170,17 +210,28 @@ export class PreKeys extends PreKeyStore {
}
async removePreKey(id: number): Promise {
- await window.textsecure.storage.protocol.removePreKey(id);
+ await window.textsecure.storage.protocol.removePreKey(this.ourUuid, id);
}
}
+export type SenderKeysOptions = Readonly<{
+ ourUuid: UUID;
+}>;
+
export class SenderKeys extends SenderKeyStore {
+ private readonly ourUuid: UUID;
+
+ constructor({ ourUuid }: SenderKeysOptions) {
+ super();
+ this.ourUuid = ourUuid;
+ }
+
async saveSenderKey(
sender: ProtocolAddress,
distributionId: Uuid,
record: SenderKeyRecord
): Promise {
- const encodedAddress = encodedNameFromAddress(sender);
+ const encodedAddress = toQualifiedAddress(this.ourUuid, sender);
await window.textsecure.storage.protocol.saveSenderKey(
encodedAddress,
@@ -193,7 +244,7 @@ export class SenderKeys extends SenderKeyStore {
sender: ProtocolAddress,
distributionId: Uuid
): Promise {
- const encodedAddress = encodedNameFromAddress(sender);
+ const encodedAddress = toQualifiedAddress(this.ourUuid, sender);
const senderKey = await window.textsecure.storage.protocol.getSenderKey(
encodedAddress,
@@ -204,12 +255,24 @@ export class SenderKeys extends SenderKeyStore {
}
}
+export type SignedPreKeysOptions = Readonly<{
+ ourUuid: UUID;
+}>;
+
export class SignedPreKeys extends SignedPreKeyStore {
+ private readonly ourUuid: UUID;
+
+ constructor({ ourUuid }: SignedPreKeysOptions) {
+ super();
+ this.ourUuid = ourUuid;
+ }
+
async saveSignedPreKey(
id: number,
record: SignedPreKeyRecord
): Promise {
await window.textsecure.storage.protocol.storeSignedPreKey(
+ this.ourUuid,
id,
freezeSignedPreKey(record),
true
@@ -218,6 +281,7 @@ export class SignedPreKeys extends SignedPreKeyStore {
async getSignedPreKey(id: number): Promise {
const signedPreKey = await window.textsecure.storage.protocol.loadSignedPreKey(
+ this.ourUuid,
id
);
diff --git a/ts/SignalProtocolStore.ts b/ts/SignalProtocolStore.ts
index b96f9281d6..7658603cc9 100644
--- a/ts/SignalProtocolStore.ts
+++ b/ts/SignalProtocolStore.ts
@@ -21,8 +21,9 @@ import {
constantTimeEqual,
fromEncodedBinaryToArrayBuffer,
typedArrayToArrayBuffer,
+ base64ToArrayBuffer,
} from './Crypto';
-import { assert } from './util/assert';
+import { assert, strictAssert } from './util/assert';
import { handleMessageSend } from './util/handleMessageSend';
import { isNotNil } from './util/isNotNil';
import { Zone } from './util/Zone';
@@ -33,19 +34,30 @@ import {
} from './util/sessionTranslation';
import {
DeviceType,
- KeyPairType,
IdentityKeyType,
+ IdentityKeyIdType,
+ KeyPairType,
+ OuterSignedPrekeyType,
+ PreKeyIdType,
+ PreKeyType,
+ SenderKeyIdType,
SenderKeyType,
+ SessionIdType,
SessionResetsType,
SessionType,
+ SignedPreKeyIdType,
SignedPreKeyType,
- OuterSignedPrekeyType,
- PreKeyType,
UnprocessedType,
UnprocessedUpdateType,
} from './textsecure/Types.d';
import { getSendOptions } from './util/getSendOptions';
import type { RemoveAllConfiguration } from './types/RemoveAllConfiguration';
+import { UUID, UUIDStringType } from './types/UUID';
+import { Address } from './types/Address';
+import {
+ QualifiedAddress,
+ QualifiedAddressStringType,
+} from './types/QualifiedAddress';
const TIMESTAMP_THRESHOLD = 5 * 1000; // 5 seconds
@@ -81,24 +93,6 @@ function validateIdentityKey(attrs: unknown): attrs is IdentityKeyType {
return true;
}
-async function normalizeEncodedAddress(
- encodedAddress: string
-): Promise {
- const [identifier, deviceId] = window.textsecure.utils.unencodeNumber(
- encodedAddress
- );
- try {
- const conv = window.ConversationController.getOrCreate(
- identifier,
- 'private'
- );
- return `${conv.get('id')}.${deviceId}`;
- } catch (e) {
- window.log.error(`could not get conversation for identifier ${identifier}`);
- throw e;
- }
-}
-
type HasIdType = {
id: T;
};
@@ -154,7 +148,7 @@ export function hydratePublicKey(identityKey: IdentityKeyType): PublicKey {
export function hydratePreKey(preKey: PreKeyType): PreKeyRecord {
const publicKey = PublicKey.deserialize(Buffer.from(preKey.publicKey));
const privateKey = PrivateKey.deserialize(Buffer.from(preKey.privateKey));
- return PreKeyRecord.new(preKey.id, publicKey, privateKey);
+ return PreKeyRecord.new(preKey.keyId, publicKey, privateKey);
}
export function hydrateSignedPreKey(
signedPreKey: SignedPreKeyType
@@ -165,7 +159,7 @@ export function hydrateSignedPreKey(
const signature = Buffer.from([]);
return SignedPreKeyRecord.new(
- signedPreKey.id,
+ signedPreKey.keyId,
createdAt,
pubKey,
privKey,
@@ -216,26 +210,32 @@ export class SignalProtocolStore extends EventsMixin {
// Cached values
- ourIdentityKey?: KeyPairType;
+ private ourIdentityKeys = new Map();
- ourRegistrationId?: number;
+ private ourRegistrationIds = new Map();
- identityKeys?: Map>;
+ identityKeys?: Map<
+ IdentityKeyIdType,
+ CacheEntryType
+ >;
- senderKeys?: Map>;
+ senderKeys?: Map<
+ SenderKeyIdType,
+ CacheEntryType
+ >;
- sessions?: Map;
+ sessions?: Map;
- preKeys?: Map>;
+ preKeys?: Map>;
signedPreKeys?: Map<
- number,
+ SignedPreKeyIdType,
CacheEntryType
>;
- senderKeyQueues: Map = new Map();
+ senderKeyQueues = new Map();
- sessionQueues: Map = new Map();
+ sessionQueues = new Map();
private currentZone?: Zone;
@@ -243,19 +243,37 @@ export class SignalProtocolStore extends EventsMixin {
private readonly zoneQueue: Array = [];
- private pendingSessions = new Map();
+ private pendingSessions = new Map();
private pendingUnprocessed = new Map();
async hydrateCaches(): Promise {
await Promise.all([
(async () => {
- const item = await window.Signal.Data.getItemById('identityKey');
- this.ourIdentityKey = item ? item.value : undefined;
+ this.ourIdentityKeys.clear();
+ const map = await window.Signal.Data.getItemById('identityKeyMap');
+ if (!map) {
+ return;
+ }
+
+ for (const key of Object.keys(map.value)) {
+ const { privKey, pubKey } = map.value[key];
+ this.ourIdentityKeys.set(new UUID(key).toString(), {
+ privKey: base64ToArrayBuffer(privKey),
+ pubKey: base64ToArrayBuffer(pubKey),
+ });
+ }
})(),
(async () => {
- const item = await window.Signal.Data.getItemById('registrationId');
- this.ourRegistrationId = item ? item.value : undefined;
+ this.ourRegistrationIds.clear();
+ const map = await window.Signal.Data.getItemById('registrationIdMap');
+ if (!map) {
+ return;
+ }
+
+ for (const key of Object.keys(map.value)) {
+ this.ourRegistrationIds.set(new UUID(key).toString(), map.value[key]);
+ }
})(),
_fillCaches(
this,
@@ -267,7 +285,7 @@ export class SignalProtocolStore extends EventsMixin {
'sessions',
window.Signal.Data.getAllSessions()
),
- _fillCaches(
+ _fillCaches(
this,
'preKeys',
window.Signal.Data.getAllPreKeys()
@@ -277,7 +295,7 @@ export class SignalProtocolStore extends EventsMixin {
'senderKeys',
window.Signal.Data.getAllSenderKeys()
),
- _fillCaches(
+ _fillCaches(
this,
'signedPreKeys',
window.Signal.Data.getAllSignedPreKeys()
@@ -285,68 +303,83 @@ export class SignalProtocolStore extends EventsMixin {
]);
}
- async getIdentityKeyPair(): Promise {
- return this.ourIdentityKey;
+ async getIdentityKeyPair(ourUuid: UUID): Promise {
+ return this.ourIdentityKeys.get(ourUuid.toString());
}
- async getLocalRegistrationId(): Promise {
- return this.ourRegistrationId;
+ async getLocalRegistrationId(ourUuid: UUID): Promise {
+ return this.ourRegistrationIds.get(ourUuid.toString());
}
// PreKeys
- async loadPreKey(keyId: number): Promise {
+ async loadPreKey(
+ ourUuid: UUID,
+ keyId: number
+ ): Promise {
if (!this.preKeys) {
throw new Error('loadPreKey: this.preKeys not yet cached!');
}
- const entry = this.preKeys.get(keyId);
+ const id: PreKeyIdType = `${ourUuid.toString()}:${keyId}`;
+
+ const entry = this.preKeys.get(id);
if (!entry) {
- window.log.error('Failed to fetch prekey:', keyId);
+ window.log.error('Failed to fetch prekey:', id);
return undefined;
}
if (entry.hydrated) {
- window.log.info('Successfully fetched prekey (cache hit):', keyId);
+ window.log.info('Successfully fetched prekey (cache hit):', id);
return entry.item;
}
const item = hydratePreKey(entry.fromDB);
- this.preKeys.set(keyId, {
+ this.preKeys.set(id, {
hydrated: true,
fromDB: entry.fromDB,
item,
});
- window.log.info('Successfully fetched prekey (cache miss):', keyId);
+ window.log.info('Successfully fetched prekey (cache miss):', id);
return item;
}
- async storePreKey(keyId: number, keyPair: KeyPairType): Promise {
+ async storePreKey(
+ ourUuid: UUID,
+ keyId: number,
+ keyPair: KeyPairType
+ ): Promise {
if (!this.preKeys) {
throw new Error('storePreKey: this.preKeys not yet cached!');
}
- if (this.preKeys.has(keyId)) {
- throw new Error(`storePreKey: prekey ${keyId} already exists!`);
+
+ const id: PreKeyIdType = `${ourUuid.toString()}:${keyId}`;
+ if (this.preKeys.has(id)) {
+ throw new Error(`storePreKey: prekey ${id} already exists!`);
}
const fromDB = {
- id: keyId,
+ id,
+ keyId,
+ ourUuid: ourUuid.toString(),
publicKey: keyPair.pubKey,
privateKey: keyPair.privKey,
};
await window.Signal.Data.createOrUpdatePreKey(fromDB);
- this.preKeys.set(keyId, {
+ this.preKeys.set(id, {
hydrated: false,
fromDB,
});
}
- async removePreKey(keyId: number): Promise {
+ async removePreKey(ourUuid: UUID, keyId: number): Promise {
if (!this.preKeys) {
throw new Error('removePreKey: this.preKeys not yet cached!');
}
+ const id: PreKeyIdType = `${ourUuid.toString()}:${keyId}`;
+
try {
this.trigger('removePreKey');
} catch (error) {
@@ -356,8 +389,8 @@ export class SignalProtocolStore extends EventsMixin {
);
}
- this.preKeys.delete(keyId);
- await window.Signal.Data.removePreKeyById(keyId);
+ this.preKeys.delete(id);
+ await window.Signal.Data.removePreKeyById(id);
}
async clearPreKeyStore(): Promise {
@@ -370,58 +403,66 @@ export class SignalProtocolStore extends EventsMixin {
// Signed PreKeys
async loadSignedPreKey(
+ ourUuid: UUID,
keyId: number
): Promise {
if (!this.signedPreKeys) {
throw new Error('loadSignedPreKey: this.signedPreKeys not yet cached!');
}
- const entry = this.signedPreKeys.get(keyId);
+ const id: SignedPreKeyIdType = `${ourUuid.toString()}:${keyId}`;
+
+ const entry = this.signedPreKeys.get(id);
if (!entry) {
- window.log.error('Failed to fetch signed prekey:', keyId);
+ window.log.error('Failed to fetch signed prekey:', id);
return undefined;
}
if (entry.hydrated) {
- window.log.info('Successfully fetched signed prekey (cache hit):', keyId);
+ window.log.info('Successfully fetched signed prekey (cache hit):', id);
return entry.item;
}
const item = hydrateSignedPreKey(entry.fromDB);
- this.signedPreKeys.set(keyId, {
+ this.signedPreKeys.set(id, {
hydrated: true,
item,
fromDB: entry.fromDB,
});
- window.log.info('Successfully fetched signed prekey (cache miss):', keyId);
+ window.log.info('Successfully fetched signed prekey (cache miss):', id);
return item;
}
- async loadSignedPreKeys(): Promise> {
+ async loadSignedPreKeys(
+ ourUuid: UUID
+ ): Promise> {
if (!this.signedPreKeys) {
throw new Error('loadSignedPreKeys: this.signedPreKeys not yet cached!');
}
- if (arguments.length > 0) {
- throw new Error('loadSignedPreKeys takes no arguments');
+ if (arguments.length > 1) {
+ throw new Error('loadSignedPreKeys takes one argument');
}
const entries = Array.from(this.signedPreKeys.values());
- return entries.map(entry => {
- const preKey = entry.fromDB;
- return {
- pubKey: preKey.publicKey,
- privKey: preKey.privateKey,
- created_at: preKey.created_at,
- keyId: preKey.id,
- confirmed: preKey.confirmed,
- };
- });
+ return entries
+ .filter(({ fromDB }) => fromDB.ourUuid === ourUuid.toString())
+ .map(entry => {
+ const preKey = entry.fromDB;
+ return {
+ pubKey: preKey.publicKey,
+ privKey: preKey.privateKey,
+ created_at: preKey.created_at,
+ keyId: preKey.keyId,
+ confirmed: preKey.confirmed,
+ };
+ });
}
// Note that this is also called in update scenarios, for confirming that signed prekeys
// have indeed been accepted by the server.
async storeSignedPreKey(
+ ourUuid: UUID,
keyId: number,
keyPair: KeyPairType,
confirmed?: boolean
@@ -430,8 +471,12 @@ export class SignalProtocolStore extends EventsMixin {
throw new Error('storeSignedPreKey: this.signedPreKeys not yet cached!');
}
+ const id: SignedPreKeyIdType = `${ourUuid.toString()}:${keyId}`;
+
const fromDB = {
- id: keyId,
+ id,
+ ourUuid: ourUuid.toString(),
+ keyId,
publicKey: keyPair.pubKey,
privateKey: keyPair.privKey,
created_at: Date.now(),
@@ -439,19 +484,20 @@ export class SignalProtocolStore extends EventsMixin {
};
await window.Signal.Data.createOrUpdateSignedPreKey(fromDB);
- this.signedPreKeys.set(keyId, {
+ this.signedPreKeys.set(id, {
hydrated: false,
fromDB,
});
}
- async removeSignedPreKey(keyId: number): Promise {
+ async removeSignedPreKey(ourUuid: UUID, keyId: number): Promise {
if (!this.signedPreKeys) {
throw new Error('removeSignedPreKey: this.signedPreKeys not yet cached!');
}
- this.signedPreKeys.delete(keyId);
- await window.Signal.Data.removeSignedPreKeyById(keyId);
+ const id: SignedPreKeyIdType = `${ourUuid.toString()}:${keyId}`;
+ this.signedPreKeys.delete(id);
+ await window.Signal.Data.removeSignedPreKeyById(id);
}
async clearSignedPreKeysStore(): Promise {
@@ -464,13 +510,12 @@ export class SignalProtocolStore extends EventsMixin {
// Sender Key Queue
async enqueueSenderKeyJob(
- encodedAddress: string,
+ qualifiedAddress: QualifiedAddress,
task: () => Promise,
zone = GLOBAL_ZONE
): Promise {
return this.withZone(zone, 'enqueueSenderKeyJob', async () => {
- const senderId = await normalizeEncodedAddress(encodedAddress);
- const queue = this._getSenderKeyQueue(senderId);
+ const queue = this._getSenderKeyQueue(qualifiedAddress);
return queue.add(task);
});
@@ -480,25 +525,28 @@ export class SignalProtocolStore extends EventsMixin {
return new PQueue({ concurrency: 1, timeout: 1000 * 60 * 2 });
}
- private _getSenderKeyQueue(senderId: string): PQueue {
- const cachedQueue = this.senderKeyQueues.get(senderId);
+ private _getSenderKeyQueue(senderId: QualifiedAddress): PQueue {
+ const cachedQueue = this.senderKeyQueues.get(senderId.toString());
if (cachedQueue) {
return cachedQueue;
}
const freshQueue = this._createSenderKeyQueue();
- this.senderKeyQueues.set(senderId, freshQueue);
+ this.senderKeyQueues.set(senderId.toString(), freshQueue);
return freshQueue;
}
// Sender Keys
- private getSenderKeyId(senderKeyId: string, distributionId: string): string {
- return `${senderKeyId}--${distributionId}`;
+ private getSenderKeyId(
+ senderKeyId: QualifiedAddress,
+ distributionId: string
+ ): SenderKeyIdType {
+ return `${senderKeyId.toString()}--${distributionId}`;
}
async saveSenderKey(
- encodedAddress: string,
+ qualifiedAddress: QualifiedAddress,
distributionId: string,
record: SenderKeyRecord
): Promise {
@@ -506,9 +554,10 @@ export class SignalProtocolStore extends EventsMixin {
throw new Error('saveSenderKey: this.senderKeys not yet cached!');
}
+ const senderId = qualifiedAddress.toString();
+
try {
- const senderId = await normalizeEncodedAddress(encodedAddress);
- const id = this.getSenderKeyId(senderId, distributionId);
+ const id = this.getSenderKeyId(qualifiedAddress, distributionId);
const fromDB: SenderKeyType = {
id,
@@ -528,22 +577,23 @@ export class SignalProtocolStore extends EventsMixin {
} catch (error) {
const errorString = error && error.stack ? error.stack : error;
window.log.error(
- `saveSenderKey: failed to save senderKey ${encodedAddress}/${distributionId}: ${errorString}`
+ `saveSenderKey: failed to save senderKey ${senderId}/${distributionId}: ${errorString}`
);
}
}
async getSenderKey(
- encodedAddress: string,
+ qualifiedAddress: QualifiedAddress,
distributionId: string
): Promise {
if (!this.senderKeys) {
throw new Error('getSenderKey: this.senderKeys not yet cached!');
}
+ const senderId = qualifiedAddress.toString();
+
try {
- const senderId = await normalizeEncodedAddress(encodedAddress);
- const id = this.getSenderKeyId(senderId, distributionId);
+ const id = this.getSenderKeyId(qualifiedAddress, distributionId);
const entry = this.senderKeys.get(id);
if (!entry) {
@@ -567,23 +617,24 @@ export class SignalProtocolStore extends EventsMixin {
} catch (error) {
const errorString = error && error.stack ? error.stack : error;
window.log.error(
- `getSenderKey: failed to load sender key ${encodedAddress}/${distributionId}: ${errorString}`
+ `getSenderKey: failed to load sender key ${senderId}/${distributionId}: ${errorString}`
);
return undefined;
}
}
async removeSenderKey(
- encodedAddress: string,
+ qualifiedAddress: QualifiedAddress,
distributionId: string
): Promise {
if (!this.senderKeys) {
throw new Error('getSenderKey: this.senderKeys not yet cached!');
}
+ const senderId = qualifiedAddress.toString();
+
try {
- const senderId = await normalizeEncodedAddress(encodedAddress);
- const id = this.getSenderKeyId(senderId, distributionId);
+ const id = this.getSenderKeyId(qualifiedAddress, distributionId);
await window.Signal.Data.removeSenderKeyById(id);
@@ -591,7 +642,7 @@ export class SignalProtocolStore extends EventsMixin {
} catch (error) {
const errorString = error && error.stack ? error.stack : error;
window.log.error(
- `removeSenderKey: failed to remove senderKey ${encodedAddress}/${distributionId}: ${errorString}`
+ `removeSenderKey: failed to remove senderKey ${senderId}/${distributionId}: ${errorString}`
);
}
}
@@ -606,13 +657,12 @@ export class SignalProtocolStore extends EventsMixin {
// Session Queue
async enqueueSessionJob(
- encodedAddress: string,
+ qualifiedAddress: QualifiedAddress,
task: () => Promise,
zone: Zone = GLOBAL_ZONE
): Promise {
return this.withZone(zone, 'enqueueSessionJob', async () => {
- const id = await normalizeEncodedAddress(encodedAddress);
- const queue = this._getSessionQueue(id);
+ const queue = this._getSessionQueue(qualifiedAddress);
return queue.add(task);
});
@@ -622,14 +672,14 @@ export class SignalProtocolStore extends EventsMixin {
return new PQueue({ concurrency: 1, timeout: 1000 * 60 * 2 });
}
- private _getSessionQueue(id: string): PQueue {
- const cachedQueue = this.sessionQueues.get(id);
+ private _getSessionQueue(id: QualifiedAddress): PQueue {
+ const cachedQueue = this.sessionQueues.get(id.toString());
if (cachedQueue) {
return cachedQueue;
}
const freshQueue = this._createSessionQueue();
- this.sessionQueues.set(id, freshQueue);
+ this.sessionQueues.set(id.toString(), freshQueue);
return freshQueue;
}
@@ -805,7 +855,7 @@ export class SignalProtocolStore extends EventsMixin {
}
async loadSession(
- encodedAddress: string,
+ qualifiedAddress: QualifiedAddress,
{ zone = GLOBAL_ZONE }: SessionTransactionOptions = {}
): Promise {
return this.withZone(zone, 'loadSession', async () => {
@@ -813,12 +863,13 @@ export class SignalProtocolStore extends EventsMixin {
throw new Error('loadSession: this.sessions not yet cached!');
}
- if (encodedAddress === null || encodedAddress === undefined) {
- throw new Error('loadSession: encodedAddress was undefined/null');
+ if (qualifiedAddress === null || qualifiedAddress === undefined) {
+ throw new Error('loadSession: qualifiedAddress was undefined/null');
}
+ const id = qualifiedAddress.toString();
+
try {
- const id = await normalizeEncodedAddress(encodedAddress);
const map = this.pendingSessions.has(id)
? this.pendingSessions
: this.sessions;
@@ -838,7 +889,7 @@ export class SignalProtocolStore extends EventsMixin {
} catch (error) {
const errorString = error && error.stack ? error.stack : error;
window.log.error(
- `loadSession: failed to load session ${encodedAddress}: ${errorString}`
+ `loadSession: failed to load session ${id}: ${errorString}`
);
return undefined;
}
@@ -846,12 +897,12 @@ export class SignalProtocolStore extends EventsMixin {
}
async loadSessions(
- encodedAddresses: Array,
+ qualifiedAddresses: Array,
{ zone = GLOBAL_ZONE }: SessionTransactionOptions = {}
): Promise> {
return this.withZone(zone, 'loadSessions', async () => {
const sessions = await Promise.all(
- encodedAddresses.map(async address =>
+ qualifiedAddresses.map(async address =>
this.loadSession(address, { zone })
)
);
@@ -889,12 +940,14 @@ export class SignalProtocolStore extends EventsMixin {
throw new Error('_maybeMigrateSession: Unknown session version type!');
}
- const keyPair = await this.getIdentityKeyPair();
+ const ourUuid = new UUID(session.ourUuid);
+
+ const keyPair = await this.getIdentityKeyPair(ourUuid);
if (!keyPair) {
throw new Error('_maybeMigrateSession: No identity key for ourself!');
}
- const localRegistrationId = await this.getLocalRegistrationId();
+ const localRegistrationId = await this.getLocalRegistrationId(ourUuid);
if (!isNumber(localRegistrationId)) {
throw new Error('_maybeMigrateSession: No registration id for ourself!');
}
@@ -915,13 +968,15 @@ export class SignalProtocolStore extends EventsMixin {
Buffer.from(sessionStructureToArrayBuffer(sessionProto))
);
- await this.storeSession(session.id, record, { zone });
+ await this.storeSession(QualifiedAddress.parse(session.id), record, {
+ zone,
+ });
return record;
}
async storeSession(
- encodedAddress: string,
+ qualifiedAddress: QualifiedAddress,
record: SessionRecord,
{ zone = GLOBAL_ZONE }: SessionTransactionOptions = {}
): Promise {
@@ -930,18 +985,25 @@ export class SignalProtocolStore extends EventsMixin {
throw new Error('storeSession: this.sessions not yet cached!');
}
- if (encodedAddress === null || encodedAddress === undefined) {
- throw new Error('storeSession: encodedAddress was undefined/null');
+ if (qualifiedAddress === null || qualifiedAddress === undefined) {
+ throw new Error('storeSession: qualifiedAddress was undefined/null');
}
- const unencoded = window.textsecure.utils.unencodeNumber(encodedAddress);
- const deviceId = parseInt(unencoded[1], 10);
+ const { uuid, deviceId } = qualifiedAddress;
+
+ const conversation = window.ConversationController.get(uuid.toString());
+ strictAssert(
+ conversation !== undefined,
+ `Conversation not found for uuid: ${uuid}`
+ );
+ const id = qualifiedAddress.toString();
try {
- const id = await normalizeEncodedAddress(encodedAddress);
const fromDB = {
id,
version: 2,
- conversationId: window.textsecure.utils.unencodeNumber(id)[0],
+ ourUuid: qualifiedAddress.ourUuid.toString(),
+ conversationId: conversation.id,
+ uuid: uuid.toString(),
deviceId,
record: record.serialize().toString('base64'),
};
@@ -962,15 +1024,14 @@ export class SignalProtocolStore extends EventsMixin {
}
} catch (error) {
const errorString = error && error.stack ? error.stack : error;
- window.log.error(
- `storeSession: Save failed fo ${encodedAddress}: ${errorString}`
- );
+ window.log.error(`storeSession: Save failed for ${id}: ${errorString}`);
throw error;
}
});
}
async getOpenDevices(
+ ourUuid: UUID,
identifiers: Array,
{ zone = GLOBAL_ZONE }: SessionTransactionOptions = {}
): Promise<{
@@ -986,27 +1047,17 @@ export class SignalProtocolStore extends EventsMixin {
}
try {
- const conversationIds = new Map();
- identifiers.forEach(identifier => {
- if (identifier === null || identifier === undefined) {
- throw new Error('getOpenDevices: identifier was undefined/null');
- }
-
- const conversation = window.ConversationController.getOrCreate(
- identifier,
- 'private'
- );
- if (!conversation) {
- throw new Error(
- `getOpenDevices: No conversationId found for identifier ${identifier}`
- );
- }
- conversationIds.set(conversation.get('id'), identifier);
- });
+ const uuidsOrIdentifiers = new Set(
+ identifiers.map(
+ identifier => UUID.lookup(identifier)?.toString() || identifier
+ )
+ );
const allSessions = this._getAllSessions();
- const entries = allSessions.filter(session =>
- conversationIds.has(session.fromDB.conversationId)
+ const entries = allSessions.filter(
+ ({ fromDB }) =>
+ fromDB.ourUuid === ourUuid.toString() &&
+ uuidsOrIdentifiers.has(fromDB.uuid)
);
const openEntries: Array<
| undefined
@@ -1043,37 +1094,21 @@ export class SignalProtocolStore extends EventsMixin {
}
const { entry, record } = item;
- const { conversationId } = entry.fromDB;
- conversationIds.delete(conversationId);
+ const { uuid } = entry.fromDB;
+ uuidsOrIdentifiers.delete(uuid);
const id = entry.fromDB.deviceId;
- const conversation = window.ConversationController.get(
- conversationId
- );
- if (!conversation) {
- throw new Error(
- `getOpenDevices: Unable to find matching conversation for ${conversationId}`
- );
- }
-
- const identifier =
- conversation.get('uuid') || conversation.get('e164');
- if (!identifier) {
- throw new Error(
- `getOpenDevices: No identifier for conversation ${conversationId}`
- );
- }
const registrationId = record.remoteRegistrationId();
return {
- identifier,
+ identifier: uuid,
id,
registrationId,
};
})
.filter(isNotNil);
- const emptyIdentifiers = Array.from(conversationIds.values());
+ const emptyIdentifiers = Array.from(uuidsOrIdentifiers.values());
return {
devices,
@@ -1089,27 +1124,31 @@ export class SignalProtocolStore extends EventsMixin {
});
}
- async getDeviceIds(identifier: string): Promise> {
- const { devices } = await this.getOpenDevices([identifier]);
+ async getDeviceIds({
+ ourUuid,
+ identifier,
+ }: Readonly<{
+ ourUuid: UUID;
+ identifier: string;
+ }>): Promise> {
+ const { devices } = await this.getOpenDevices(ourUuid, [identifier]);
return devices.map((device: DeviceType) => device.id);
}
- async removeSession(encodedAddress: string): Promise {
+ async removeSession(qualifiedAddress: QualifiedAddress): Promise {
return this.withZone(GLOBAL_ZONE, 'removeSession', async () => {
if (!this.sessions) {
throw new Error('removeSession: this.sessions not yet cached!');
}
- window.log.info('removeSession: deleting session for', encodedAddress);
+ const id = qualifiedAddress.toString();
+ window.log.info('removeSession: deleting session for', id);
try {
- const id = await normalizeEncodedAddress(encodedAddress);
await window.Signal.Data.removeSessionById(id);
this.sessions.delete(id);
this.pendingSessions.delete(id);
} catch (e) {
- window.log.error(
- `removeSession: Failed to delete session for ${encodedAddress}`
- );
+ window.log.error(`removeSession: Failed to delete session for ${id}`);
}
});
}
@@ -1127,6 +1166,7 @@ export class SignalProtocolStore extends EventsMixin {
window.log.info('removeAllSessions: deleting sessions for', identifier);
const id = window.ConversationController.getConversationId(identifier);
+ strictAssert(id, `Conversation not found: ${identifier}`);
const entries = Array.from(this.sessions.values());
@@ -1138,7 +1178,7 @@ export class SignalProtocolStore extends EventsMixin {
}
}
- await window.Signal.Data.removeSessionsByConversation(identifier);
+ await window.Signal.Data.removeSessionsByConversation(id);
});
}
@@ -1147,8 +1187,10 @@ export class SignalProtocolStore extends EventsMixin {
return;
}
+ const addr = QualifiedAddress.parse(entry.fromDB.id);
+
await this.enqueueSessionJob(
- entry.fromDB.id,
+ addr,
async () => {
const item = entry.hydrated
? entry.item
@@ -1160,21 +1202,21 @@ export class SignalProtocolStore extends EventsMixin {
item.archiveCurrentState();
- await this.storeSession(entry.fromDB.id, item, { zone });
+ await this.storeSession(addr, item, { zone });
},
zone
);
}
- async archiveSession(encodedAddress: string): Promise {
+ async archiveSession(qualifiedAddress: QualifiedAddress): Promise {
return this.withZone(GLOBAL_ZONE, 'archiveSession', async () => {
if (!this.sessions) {
throw new Error('archiveSession: this.sessions not yet cached!');
}
- window.log.info(`archiveSession: session for ${encodedAddress}`);
+ const id = qualifiedAddress.toString();
- const id = await normalizeEncodedAddress(encodedAddress);
+ window.log.info(`archiveSession: session for ${id}`);
const entry = this.pendingSessions.get(id) || this.sessions.get(id);
@@ -1183,7 +1225,7 @@ export class SignalProtocolStore extends EventsMixin {
}
async archiveSiblingSessions(
- encodedAddress: string,
+ encodedAddress: Address,
{ zone = GLOBAL_ZONE }: SessionTransactionOptions = {}
): Promise {
return this.withZone(zone, 'archiveSiblingSessions', async () => {
@@ -1195,18 +1237,16 @@ export class SignalProtocolStore extends EventsMixin {
window.log.info(
'archiveSiblingSessions: archiving sibling sessions for',
- encodedAddress
+ encodedAddress.toString()
);
- const id = await normalizeEncodedAddress(encodedAddress);
- const [identifier, deviceId] = window.textsecure.utils.unencodeNumber(id);
- const deviceIdNumber = parseInt(deviceId, 10);
+ const { uuid, deviceId } = encodedAddress;
const allEntries = this._getAllSessions();
const entries = allEntries.filter(
entry =>
- entry.fromDB.conversationId === identifier &&
- entry.fromDB.deviceId !== deviceIdNumber
+ entry.fromDB.uuid === uuid.toString() &&
+ entry.fromDB.deviceId !== deviceId
);
await Promise.all(
@@ -1217,7 +1257,7 @@ export class SignalProtocolStore extends EventsMixin {
});
}
- async archiveAllSessions(identifier: string): Promise {
+ async archiveAllSessions(uuid: UUID): Promise {
return this.withZone(GLOBAL_ZONE, 'archiveAllSessions', async () => {
if (!this.sessions) {
throw new Error('archiveAllSessions: this.sessions not yet cached!');
@@ -1225,14 +1265,12 @@ export class SignalProtocolStore extends EventsMixin {
window.log.info(
'archiveAllSessions: archiving all sessions for',
- identifier
+ uuid.toString()
);
- const id = window.ConversationController.getConversationId(identifier);
-
const allEntries = this._getAllSessions();
const entries = allEntries.filter(
- entry => entry.fromDB.conversationId === id
+ entry => entry.fromDB.uuid === uuid.toString()
);
await Promise.all(
@@ -1253,8 +1291,8 @@ export class SignalProtocolStore extends EventsMixin {
});
}
- async lightSessionReset(uuid: string, deviceId: number): Promise {
- const id = `${uuid}.${deviceId}`;
+ async lightSessionReset(qualifiedAddress: QualifiedAddress): Promise {
+ const id = qualifiedAddress.toString();
const sessionResets = window.storage.get(
'sessionResets',
@@ -1275,9 +1313,11 @@ export class SignalProtocolStore extends EventsMixin {
window.storage.put('sessionResets', sessionResets);
try {
+ const { uuid } = qualifiedAddress;
+
// First, fetch this conversation
const conversationId = window.ConversationController.ensureContactIds({
- uuid,
+ uuid: uuid.toString(),
});
assert(conversationId, `lightSessionReset/${id}: missing conversationId`);
@@ -1287,12 +1327,17 @@ export class SignalProtocolStore extends EventsMixin {
window.log.warn(`lightSessionReset/${id}: Resetting session`);
// Archive open session with this device
- await this.archiveSession(id);
+ await this.archiveSession(qualifiedAddress);
// Send a null message with newly-created session
const sendOptions = await getSendOptions(conversation.attributes);
const result = await handleMessageSend(
- window.textsecure.messaging.sendNullMessage({ uuid }, sendOptions),
+ window.textsecure.messaging.sendNullMessage(
+ {
+ uuid: uuid.toString(),
+ },
+ sendOptions
+ ),
{ messageIds: [], sendType: 'nullMessage' }
);
@@ -1315,19 +1360,14 @@ export class SignalProtocolStore extends EventsMixin {
// Identity Keys
- getIdentityRecord(identifier: string): IdentityKeyType | undefined {
+ getIdentityRecord(uuid: UUID): IdentityKeyType | undefined {
if (!this.identityKeys) {
throw new Error('getIdentityRecord: this.identityKeys not yet cached!');
}
- try {
- const id = window.ConversationController.getConversationId(identifier);
- if (!id) {
- throw new Error(
- `getIdentityRecord: No conversation id for identifier ${identifier}`
- );
- }
+ const id = uuid.toString();
+ try {
const entry = this.identityKeys.get(id);
if (!entry) {
return undefined;
@@ -1336,34 +1376,74 @@ export class SignalProtocolStore extends EventsMixin {
return entry.fromDB;
} catch (e) {
window.log.error(
- `getIdentityRecord: Failed to get identity record for identifier ${identifier}`
+ `getIdentityRecord: Failed to get identity record for identifier ${id}`
);
return undefined;
}
}
+ async getOrMigrateIdentityRecord(
+ uuid: UUID
+ ): Promise {
+ if (!this.identityKeys) {
+ throw new Error(
+ 'getOrMigrateIdentityRecord: this.identityKeys not yet cached!'
+ );
+ }
+
+ const result = this.getIdentityRecord(uuid);
+ if (result) {
+ return result;
+ }
+
+ const newId = uuid.toString();
+ const conversation = window.ConversationController.get(newId);
+ if (!conversation) {
+ return undefined;
+ }
+
+ const conversationId = new UUID(conversation.id).toString();
+ const record = this.identityKeys.get(`conversation:${conversationId}`);
+ if (!record) {
+ return undefined;
+ }
+
+ const newRecord = {
+ ...record.fromDB,
+ id: newId,
+ };
+
+ window.log.info(
+ `SignalProtocolStore: migrating identity key from ${record.fromDB.id} ` +
+ `to ${newRecord.id}`
+ );
+
+ await this._saveIdentityKey(newRecord);
+
+ this.identityKeys.delete(record.fromDB.id);
+ await window.Signal.Data.removeIdentityKeyById(record.fromDB.id);
+
+ return newRecord;
+ }
+
async isTrustedIdentity(
- encodedAddress: string,
+ encodedAddress: Address,
publicKey: ArrayBuffer,
direction: number
): Promise {
if (!this.identityKeys) {
- throw new Error('getIdentityRecord: this.identityKeys not yet cached!');
+ throw new Error('isTrustedIdentity: this.identityKeys not yet cached!');
}
if (encodedAddress === null || encodedAddress === undefined) {
throw new Error('isTrustedIdentity: encodedAddress was undefined/null');
}
- const identifier = window.textsecure.utils.unencodeNumber(
- encodedAddress
- )[0];
- const ourNumber = window.textsecure.storage.user.getNumber();
- const ourUuid = window.textsecure.storage.user.getUuid();
- const isOurIdentifier =
- (ourNumber && identifier === ourNumber) ||
- (ourUuid && identifier === ourUuid);
+ const ourUuid = window.textsecure.storage.user.getCheckedUuid();
+ const isOurIdentifier = encodedAddress.uuid.isEqual(ourUuid);
- const identityRecord = this.getIdentityRecord(identifier);
+ const identityRecord = await this.getOrMigrateIdentityRecord(
+ encodedAddress.uuid
+ );
if (isOurIdentifier) {
if (identityRecord && identityRecord.publicKey) {
@@ -1418,12 +1498,11 @@ export class SignalProtocolStore extends EventsMixin {
return true;
}
- async loadIdentityKey(identifier: string): Promise {
- if (identifier === null || identifier === undefined) {
- throw new Error('loadIdentityKey: identifier was undefined/null');
+ async loadIdentityKey(uuid: UUID): Promise {
+ if (uuid === null || uuid === undefined) {
+ throw new Error('loadIdentityKey: uuid was undefined/null');
}
- const id = window.textsecure.utils.unencodeNumber(identifier)[0];
- const identityRecord = this.getIdentityRecord(id);
+ const identityRecord = await this.getOrMigrateIdentityRecord(uuid);
if (identityRecord) {
return identityRecord.publicKey;
@@ -1447,7 +1526,7 @@ export class SignalProtocolStore extends EventsMixin {
}
async saveIdentity(
- encodedAddress: string,
+ encodedAddress: Address,
publicKey: ArrayBuffer,
nonblockingApproval = false,
{ zone }: SessionTransactionOptions = {}
@@ -1468,14 +1547,11 @@ export class SignalProtocolStore extends EventsMixin {
nonblockingApproval = false;
}
- const identifier = window.textsecure.utils.unencodeNumber(
- encodedAddress
- )[0];
- const identityRecord = this.getIdentityRecord(identifier);
- const id = window.ConversationController.getOrCreate(
- identifier,
- 'private'
- ).get('id');
+ const identityRecord = await this.getOrMigrateIdentityRecord(
+ encodedAddress.uuid
+ );
+
+ const id = encodedAddress.uuid.toString();
if (!identityRecord || !identityRecord.publicKey) {
// Lookup failed, or the current key was removed, so save this one.
@@ -1516,7 +1592,7 @@ export class SignalProtocolStore extends EventsMixin {
});
try {
- this.trigger('keychange', identifier);
+ this.trigger('keychange', encodedAddress.uuid);
} catch (error) {
window.log.error(
'saveIdentity: error triggering keychange:',
@@ -1526,7 +1602,9 @@ export class SignalProtocolStore extends EventsMixin {
// Pass the zone to facilitate transactional session use in
// MessageReceiver.ts
- await this.archiveSiblingSessions(encodedAddress, { zone });
+ await this.archiveSiblingSessions(encodedAddress, {
+ zone,
+ });
return true;
}
@@ -1551,24 +1629,17 @@ export class SignalProtocolStore extends EventsMixin {
}
async saveIdentityWithAttributes(
- encodedAddress: string,
+ uuid: UUID,
attributes: Partial
): Promise {
- if (encodedAddress === null || encodedAddress === undefined) {
- throw new Error(
- 'saveIdentityWithAttributes: encodedAddress was undefined/null'
- );
+ if (uuid === null || uuid === undefined) {
+ throw new Error('saveIdentityWithAttributes: uuid was undefined/null');
}
- const identifier = window.textsecure.utils.unencodeNumber(
- encodedAddress
- )[0];
- const identityRecord = this.getIdentityRecord(identifier);
- const conv = window.ConversationController.getOrCreate(
- identifier,
- 'private'
- );
- const id = conv.get('id');
+ const identityRecord = await this.getOrMigrateIdentityRecord(uuid);
+ const id = uuid.toString();
+
+ window.ConversationController.getOrCreate(id, 'private');
const updates: Partial = {
...identityRecord,
@@ -1581,24 +1652,18 @@ export class SignalProtocolStore extends EventsMixin {
}
}
- async setApproval(
- encodedAddress: string,
- nonblockingApproval: boolean
- ): Promise {
- if (encodedAddress === null || encodedAddress === undefined) {
- throw new Error('setApproval: encodedAddress was undefined/null');
+ async setApproval(uuid: UUID, nonblockingApproval: boolean): Promise {
+ if (uuid === null || uuid === undefined) {
+ throw new Error('setApproval: uuid was undefined/null');
}
if (typeof nonblockingApproval !== 'boolean') {
throw new Error('setApproval: Invalid approval status');
}
- const identifier = window.textsecure.utils.unencodeNumber(
- encodedAddress
- )[0];
- const identityRecord = this.getIdentityRecord(identifier);
+ const identityRecord = await this.getOrMigrateIdentityRecord(uuid);
if (!identityRecord) {
- throw new Error(`setApproval: No identity record for ${identifier}`);
+ throw new Error(`setApproval: No identity record for ${uuid}`);
}
identityRecord.nonblockingApproval = nonblockingApproval;
@@ -1606,12 +1671,12 @@ export class SignalProtocolStore extends EventsMixin {
}
async setVerified(
- encodedAddress: string,
+ uuid: UUID,
verifiedStatus: number,
publicKey?: ArrayBuffer
): Promise {
- if (encodedAddress === null || encodedAddress === undefined) {
- throw new Error('setVerified: encodedAddress was undefined/null');
+ if (uuid === null || uuid === undefined) {
+ throw new Error('setVerified: uuid was undefined/null');
}
if (!validateVerifiedStatus(verifiedStatus)) {
throw new Error('setVerified: Invalid verified status');
@@ -1620,10 +1685,10 @@ export class SignalProtocolStore extends EventsMixin {
throw new Error('setVerified: Invalid public key');
}
- const identityRecord = this.getIdentityRecord(encodedAddress);
+ const identityRecord = await this.getOrMigrateIdentityRecord(uuid);
if (!identityRecord) {
- throw new Error(`setVerified: No identity record for ${encodedAddress}`);
+ throw new Error(`setVerified: No identity record for ${uuid.toString()}`);
}
if (!publicKey || constantTimeEqual(identityRecord.publicKey, publicKey)) {
@@ -1639,14 +1704,14 @@ export class SignalProtocolStore extends EventsMixin {
}
}
- async getVerified(identifier: string): Promise {
- if (identifier === null || identifier === undefined) {
- throw new Error('getVerified: identifier was undefined/null');
+ async getVerified(uuid: UUID): Promise {
+ if (uuid === null || uuid === undefined) {
+ throw new Error('getVerified: uuid was undefined/null');
}
- const identityRecord = this.getIdentityRecord(identifier);
+ const identityRecord = await this.getOrMigrateIdentityRecord(uuid);
if (!identityRecord) {
- throw new Error(`getVerified: No identity record for ${identifier}`);
+ throw new Error(`getVerified: No identity record for ${uuid}`);
}
const verifiedStatus = identityRecord.verified;
@@ -1659,38 +1724,32 @@ export class SignalProtocolStore extends EventsMixin {
// Resolves to true if a new identity key was saved
processContactSyncVerificationState(
- identifier: string,
+ uuid: UUID,
verifiedStatus: number,
publicKey: ArrayBuffer
): Promise {
if (verifiedStatus === VerifiedStatus.UNVERIFIED) {
- return this.processUnverifiedMessage(
- identifier,
- verifiedStatus,
- publicKey
- );
+ return this.processUnverifiedMessage(uuid, verifiedStatus, publicKey);
}
- return this.processVerifiedMessage(identifier, verifiedStatus, publicKey);
+ return this.processVerifiedMessage(uuid, verifiedStatus, publicKey);
}
// This function encapsulates the non-Java behavior, since the mobile apps don't
// currently receive contact syncs and therefore will see a verify sync with
// UNVERIFIED status
async processUnverifiedMessage(
- identifier: string,
+ uuid: UUID,
verifiedStatus: number,
publicKey?: ArrayBuffer
): Promise {
- if (identifier === null || identifier === undefined) {
- throw new Error(
- 'processUnverifiedMessage: identifier was undefined/null'
- );
+ if (uuid === null || uuid === undefined) {
+ throw new Error('processUnverifiedMessage: uuid was undefined/null');
}
if (publicKey !== undefined && !(publicKey instanceof ArrayBuffer)) {
throw new Error('processUnverifiedMessage: Invalid public key');
}
- const identityRecord = this.getIdentityRecord(identifier);
+ const identityRecord = await this.getOrMigrateIdentityRecord(uuid);
let isEqual = false;
@@ -1703,12 +1762,12 @@ export class SignalProtocolStore extends EventsMixin {
isEqual &&
identityRecord.verified !== VerifiedStatus.UNVERIFIED
) {
- await this.setVerified(identifier, verifiedStatus, publicKey);
+ await this.setVerified(uuid, verifiedStatus, publicKey);
return false;
}
if (!identityRecord || !isEqual) {
- await this.saveIdentityWithAttributes(identifier, {
+ await this.saveIdentityWithAttributes(uuid, {
publicKey,
verified: verifiedStatus,
firstUse: false,
@@ -1718,7 +1777,7 @@ export class SignalProtocolStore extends EventsMixin {
if (identityRecord && !isEqual) {
try {
- this.trigger('keychange', identifier);
+ this.trigger('keychange', uuid);
} catch (error) {
window.log.error(
'processUnverifiedMessage: error triggering keychange:',
@@ -1726,7 +1785,7 @@ export class SignalProtocolStore extends EventsMixin {
);
}
- await this.archiveAllSessions(identifier);
+ await this.archiveAllSessions(uuid);
return true;
}
@@ -1742,12 +1801,12 @@ export class SignalProtocolStore extends EventsMixin {
// This matches the Java method as of
// https://github.com/signalapp/Signal-Android/blob/d0bb68e1378f689e4d10ac6a46014164992ca4e4/src/org/thoughtcrime/securesms/util/IdentityUtil.java#L188
async processVerifiedMessage(
- identifier: string,
+ uuid: UUID,
verifiedStatus: number,
publicKey?: ArrayBuffer
): Promise {
- if (identifier === null || identifier === undefined) {
- throw new Error('processVerifiedMessage: identifier was undefined/null');
+ if (uuid === null || uuid === undefined) {
+ throw new Error('processVerifiedMessage: uuid was undefined/null');
}
if (!validateVerifiedStatus(verifiedStatus)) {
throw new Error('processVerifiedMessage: Invalid verified status');
@@ -1756,7 +1815,7 @@ export class SignalProtocolStore extends EventsMixin {
throw new Error('processVerifiedMessage: Invalid public key');
}
- const identityRecord = this.getIdentityRecord(identifier);
+ const identityRecord = await this.getOrMigrateIdentityRecord(uuid);
let isEqual = false;
@@ -1777,7 +1836,7 @@ export class SignalProtocolStore extends EventsMixin {
identityRecord.verified !== VerifiedStatus.DEFAULT &&
verifiedStatus === VerifiedStatus.DEFAULT
) {
- await this.setVerified(identifier, verifiedStatus, publicKey);
+ await this.setVerified(uuid, verifiedStatus, publicKey);
return false;
}
@@ -1787,7 +1846,7 @@ export class SignalProtocolStore extends EventsMixin {
(identityRecord && !isEqual) ||
(identityRecord && identityRecord.verified !== VerifiedStatus.VERIFIED))
) {
- await this.saveIdentityWithAttributes(identifier, {
+ await this.saveIdentityWithAttributes(uuid, {
publicKey,
verified: verifiedStatus,
firstUse: false,
@@ -1797,7 +1856,7 @@ export class SignalProtocolStore extends EventsMixin {
if (identityRecord && !isEqual) {
try {
- this.trigger('keychange', identifier);
+ this.trigger('keychange', uuid);
} catch (error) {
window.log.error(
'processVerifiedMessage error triggering keychange:',
@@ -1805,7 +1864,7 @@ export class SignalProtocolStore extends EventsMixin {
);
}
- await this.archiveAllSessions(identifier);
+ await this.archiveAllSessions(uuid);
// true signifies that we overwrote a previous key with a new one
return true;
@@ -1818,14 +1877,14 @@ export class SignalProtocolStore extends EventsMixin {
return false;
}
- isUntrusted(identifier: string): boolean {
- if (identifier === null || identifier === undefined) {
- throw new Error('isUntrusted: identifier was undefined/null');
+ isUntrusted(uuid: UUID): boolean {
+ if (uuid === null || uuid === undefined) {
+ throw new Error('isUntrusted: uuid was undefined/null');
}
- const identityRecord = this.getIdentityRecord(identifier);
+ const identityRecord = this.getIdentityRecord(uuid);
if (!identityRecord) {
- throw new Error(`isUntrusted: No identity record for ${identifier}`);
+ throw new Error(`isUntrusted: No identity record for ${uuid.toString()}`);
}
if (
@@ -1839,17 +1898,15 @@ export class SignalProtocolStore extends EventsMixin {
return false;
}
- async removeIdentityKey(identifier: string): Promise {
+ async removeIdentityKey(uuid: UUID): Promise {
if (!this.identityKeys) {
throw new Error('removeIdentityKey: this.identityKeys not yet cached!');
}
- const id = window.ConversationController.getConversationId(identifier);
- if (id) {
- this.identityKeys.delete(id);
- await window.Signal.Data.removeIdentityKeyById(id);
- await this.removeAllSessions(id);
- }
+ const id = uuid.toString();
+ this.identityKeys.delete(id);
+ await window.Signal.Data.removeIdentityKeyById(id);
+ await this.removeAllSessions(id);
}
// Not yet processed messages - for resiliency
diff --git a/ts/background.ts b/ts/background.ts
index a5da8d2c74..b8b7e6854e 100644
--- a/ts/background.ts
+++ b/ts/background.ts
@@ -66,6 +66,7 @@ import {
EnvelopeEvent,
} from './textsecure/messageReceiverEvents';
import type { WebAPIType } from './textsecure/WebAPI';
+import * as KeyChangeListener from './textsecure/KeyChangeListener';
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
import { getSendOptions } from './util/getSendOptions';
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
@@ -497,7 +498,7 @@ export async function startApp(): Promise {
window.document.title = window.getTitle();
- window.Whisper.KeyChangeListener.init(window.textsecure.storage.protocol);
+ KeyChangeListener.init(window.textsecure.storage.protocol);
window.textsecure.storage.protocol.on('removePreKey', () => {
window.getAccountManager().refreshPreKeys();
});
@@ -921,7 +922,7 @@ export async function startApp(): Promise {
conversation.format()
);
const ourNumber = window.textsecure.storage.user.getNumber();
- const ourUuid = window.textsecure.storage.user.getUuid();
+ const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
const ourConversationId = window.ConversationController.getOurConversationId();
const themeSetting = window.Events.getThemeSetting();
@@ -1065,7 +1066,7 @@ export async function startApp(): Promise {
window.Whisper.events.on('userChanged', (reconnect = false) => {
const newDeviceId = window.textsecure.storage.user.getDeviceId();
const newNumber = window.textsecure.storage.user.getNumber();
- const newUuid = window.textsecure.storage.user.getUuid();
+ const newUuid = window.textsecure.storage.user.getUuid()?.toString();
const ourConversation = window.ConversationController.getOurConversation();
if (ourConversation?.get('e164') !== newNumber) {
@@ -2136,30 +2137,9 @@ export async function startApp(): Promise {
const deviceId = window.textsecure.storage.user.getDeviceId();
- // If we didn't capture a UUID on registration, go get it from the server
if (!window.textsecure.storage.user.getUuid()) {
- try {
- const { uuid } = await server.whoami();
- assert(deviceId, 'We should have device id');
- window.textsecure.storage.user.setUuidAndDeviceId(uuid, deviceId);
- const ourNumber = window.textsecure.storage.user.getNumber();
-
- assert(ourNumber, 'We should have number');
- const me = await window.ConversationController.getOrCreateAndWait(
- ourNumber,
- 'private'
- );
- me.updateUuid(uuid);
-
- await server.authenticate(
- window.textsecure.storage.user.getWebAPICredentials()
- );
- } catch (error) {
- window.log.error(
- 'Error: Unable to retrieve UUID from service.',
- error && error.stack ? error.stack : error
- );
- }
+ window.log.error('UUID not captured during registration, unlinking');
+ return unlinkAndDisconnect(RemoveAllConfiguration.Full);
}
if (connectCount === 1) {
@@ -2587,7 +2567,7 @@ export async function startApp(): Promise {
(details.number &&
details.number === window.textsecure.storage.user.getNumber()) ||
(details.uuid &&
- details.uuid === window.textsecure.storage.user.getUuid())
+ details.uuid === window.textsecure.storage.user.getUuid()?.toString())
) {
// special case for syncing details about ourselves
if (details.profileKey) {
@@ -2845,7 +2825,7 @@ export async function startApp(): Promise {
});
function onEnvelopeReceived({ envelope }: EnvelopeEvent) {
- const ourUuid = window.textsecure.storage.user.getUuid();
+ const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
if (envelope.sourceUuid && envelope.sourceUuid !== ourUuid) {
window.ConversationController.ensureContactIds({
e164: envelope.source,
@@ -3083,7 +3063,7 @@ export async function startApp(): Promise {
return new window.Whisper.Message(({
source: window.textsecure.storage.user.getNumber(),
- sourceUuid: window.textsecure.storage.user.getUuid(),
+ sourceUuid: window.textsecure.storage.user.getUuid()?.toString(),
sourceDevice: data.device,
sent_at: timestamp,
serverTimestamp: data.serverTimestamp,
@@ -3216,7 +3196,7 @@ export async function startApp(): Promise {
const { data, confirm } = event;
const source = window.textsecure.storage.user.getNumber();
- const sourceUuid = window.textsecure.storage.user.getUuid();
+ const sourceUuid = window.textsecure.storage.user.getUuid()?.toString();
strictAssert(source && sourceUuid, 'Missing user number and uuid');
const messageDescriptor = getMessageDescriptor({
@@ -3492,7 +3472,7 @@ export async function startApp(): Promise {
switch (eventType) {
case FETCH_LATEST_ENUM.LOCAL_PROFILE: {
- const ourUuid = window.textsecure.storage.user.getUuid();
+ const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
const ourE164 = window.textsecure.storage.user.getNumber();
await getProfile(ourUuid, ourE164);
break;
diff --git a/ts/groups.ts b/ts/groups.ts
index 63f70e949d..fbb21d12a0 100644
--- a/ts/groups.ts
+++ b/ts/groups.ts
@@ -550,19 +550,12 @@ function buildGroupProto(
);
}
- const me = window.ConversationController.get(ourConversationId);
- if (!me) {
- throw new Error(
- `buildGroupProto/${logId}: unable to find our own conversation!`
- );
- }
+ const ourUuid = window.storage.user.getCheckedUuid();
- const ourUuid = me.get('uuid');
- if (!ourUuid) {
- throw new Error(`buildGroupProto/${logId}: unable to find our own uuid!`);
- }
-
- const ourUuidCipherTextBuffer = encryptUuid(clientZkGroupCipher, ourUuid);
+ const ourUuidCipherTextBuffer = encryptUuid(
+ clientZkGroupCipher,
+ ourUuid.toString()
+ );
proto.membersPendingProfileKey = (attributes.pendingMembersV2 || []).map(
item => {
@@ -627,15 +620,11 @@ export async function buildAddMembersChange(
);
const clientZkGroupCipher = getClientZkGroupCipher(secretParams);
- const ourConversationId = window.ConversationController.getOurConversationIdOrThrow();
- const ourConversation = window.ConversationController.get(ourConversationId);
- const ourUuid = ourConversation?.get('uuid');
- if (!ourUuid) {
- throw new Error(
- `buildAddMembersChange/${logId}: unable to find our own UUID!`
- );
- }
- const ourUuidCipherTextBuffer = encryptUuid(clientZkGroupCipher, ourUuid);
+ const ourUuid = window.storage.user.getCheckedUuid();
+ const ourUuidCipherTextBuffer = encryptUuid(
+ clientZkGroupCipher,
+ ourUuid.toString()
+ );
const now = Date.now();
@@ -1727,10 +1716,12 @@ export async function createGroupV2({
timestamp,
});
+ const ourUuid = window.storage.user.getCheckedUuid();
+
const createdTheGroupMessage: MessageAttributesType = {
...generateBasicMessage(),
type: 'group-v2-change',
- sourceUuid: conversation.ourUuid,
+ sourceUuid: ourUuid.toString(),
conversationId: conversation.id,
received_at: window.Signal.Util.incrementMessageCounter(),
received_at_ms: timestamp,
diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts
index b54635cd4d..3ced9c0969 100644
--- a/ts/models/conversations.ts
+++ b/ts/models/conversations.ts
@@ -37,6 +37,7 @@ import { missingCaseError } from '../util/missingCaseError';
import { sniffImageMimeType } from '../util/sniffImageMimeType';
import { isValidE164 } from '../util/isValidE164';
import { MIMEType, IMAGE_WEBP } from '../types/MIME';
+import { UUID } from '../types/UUID';
import {
arrayBufferToBase64,
base64ToArrayBuffer,
@@ -157,8 +158,6 @@ export class ConversationModel extends window.Backbone
jobQueue?: typeof window.PQueueType;
- ourUuid?: string;
-
storeName?: string | null;
throttledBumpTyping?: () => void;
@@ -239,7 +238,6 @@ export class ConversationModel extends window.Backbone
this.storeName = 'conversations';
- this.ourUuid = window.textsecure.storage.user.getUuid();
this.verifiedEnum = window.textsecure.storage.protocol.VerifiedStatus;
// This may be overridden by window.ConversationController.getOrCreate, and signify
@@ -2105,7 +2103,14 @@ export class ConversationModel extends window.Backbone
}
async safeGetVerified(): Promise {
- const promise = window.textsecure.storage.protocol.getVerified(this.id);
+ const uuid = this.get('uuid');
+ if (!uuid) {
+ return window.textsecure.storage.protocol.VerifiedStatus.DEFAULT;
+ }
+
+ const promise = window.textsecure.storage.protocol.getVerified(
+ new UUID(uuid)
+ );
return promise.catch(
() => window.textsecure.storage.protocol.VerifiedStatus.DEFAULT
);
@@ -2182,21 +2187,31 @@ export class ConversationModel extends window.Backbone
);
}
+ const uuid = this.get('uuid');
const beginningVerified = this.get('verified');
let keyChange;
if (options.viaSyncMessage) {
+ strictAssert(
+ uuid,
+ `Sync message didn't update uuid for conversation: ${this.id}`
+ );
+
// handle the incoming key from the sync messages - need different
// behavior if that key doesn't match the current key
keyChange = await window.textsecure.storage.protocol.processVerifiedMessage(
- this.id,
+ new UUID(uuid),
verified,
options.key || undefined
);
- } else {
+ } else if (uuid) {
keyChange = await window.textsecure.storage.protocol.setVerified(
- this.id,
+ new UUID(uuid),
verified
);
+ } else {
+ window.log.warn(
+ `_setVerified(${this.id}): no uuid to update protocol storage`
+ );
}
this.set({ verified });
@@ -2227,12 +2242,8 @@ export class ConversationModel extends window.Backbone
local: !options.viaSyncMessage,
});
}
- if (!options.viaSyncMessage) {
- await this.sendVerifySyncMessage(
- this.get('e164'),
- this.get('uuid'),
- verified
- );
+ if (!options.viaSyncMessage && uuid) {
+ await this.sendVerifySyncMessage(this.get('e164'), uuid, verified);
}
return keyChange;
@@ -2240,7 +2251,7 @@ export class ConversationModel extends window.Backbone
async sendVerifySyncMessage(
e164: string | undefined,
- uuid: string | undefined,
+ uuid: string,
state: number
): Promise {
const identifier = uuid || e164;
@@ -2268,7 +2279,7 @@ export class ConversationModel extends window.Backbone
const options = { ...sendOptions, ...contactSendOptions };
const key = await window.textsecure.storage.protocol.loadIdentityKey(
- identifier
+ UUID.checkedLookup(identifier)
);
if (!key) {
throw new Error(
@@ -2353,12 +2364,20 @@ export class ConversationModel extends window.Backbone
);
}
- return window.textsecure.storage.protocol.setApproval(this.id, true);
+ const uuid = this.get('uuid');
+ if (!uuid) {
+ window.log.warn(`setApproved(${this.id}): no uuid, ignoring`);
+ return;
+ }
+
+ return window.textsecure.storage.protocol.setApproval(new UUID(uuid), true);
}
safeIsUntrusted(): boolean {
+ const uuid = this.get('uuid');
try {
- return window.textsecure.storage.protocol.isUntrusted(this.id);
+ strictAssert(uuid, `No uuid for conversation: ${this.id}`);
+ return window.textsecure.storage.protocol.isUntrusted(new UUID(uuid));
} catch (err) {
return false;
}
@@ -2526,11 +2545,11 @@ export class ConversationModel extends window.Backbone
await this.notify(model);
}
- async addKeyChange(keyChangedId: string): Promise {
+ async addKeyChange(keyChangedId: UUID): Promise {
window.log.info(
'adding key change advisory for',
this.idForLogging(),
- keyChangedId,
+ keyChangedId.toString(),
this.get('timestamp')
);
@@ -2541,7 +2560,7 @@ export class ConversationModel extends window.Backbone
sent_at: this.get('timestamp'),
received_at: window.Signal.Util.incrementMessageCounter(),
received_at_ms: timestamp,
- key_changed: keyChangedId,
+ key_changed: keyChangedId.toString(),
readStatus: ReadStatus.Unread,
schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY,
// TODO: DESKTOP-722
@@ -4839,7 +4858,7 @@ export class ConversationModel extends window.Backbone
return;
}
- const ourUuid = window.textsecure.storage.user.getUuid();
+ const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
const mentionsMe = (message.get('bodyRanges') || []).some(
range => range.mentionUuid && range.mentionUuid === ourUuid
);
diff --git a/ts/models/messages.ts b/ts/models/messages.ts
index c24f145686..e39d62dd05 100644
--- a/ts/models/messages.ts
+++ b/ts/models/messages.ts
@@ -221,7 +221,7 @@ export class MessageModel extends window.Backbone.Model {
this.CURRENT_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.CURRENT;
this.INITIAL_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.INITIAL;
- this.OUR_UUID = window.textsecure.storage.user.getUuid();
+ this.OUR_UUID = window.textsecure.storage.user.getUuid()?.toString();
this.on('change', this.notifyRedux);
}
@@ -2881,7 +2881,8 @@ export class MessageModel extends window.Backbone.Model {
const profileKey = dataMessage.profileKey.toString('base64');
if (
source === window.textsecure.storage.user.getNumber() ||
- sourceUuid === window.textsecure.storage.user.getUuid()
+ sourceUuid ===
+ window.textsecure.storage.user.getUuid()?.toString()
) {
conversation.set({ profileSharing: true });
} else if (isDirectConversation(conversation.attributes)) {
diff --git a/ts/services/calling.ts b/ts/services/calling.ts
index 0a452ae237..6ecb97a51d 100644
--- a/ts/services/calling.ts
+++ b/ts/services/calling.ts
@@ -53,6 +53,7 @@ import {
ProcessGroupCallRingRequestResult,
} from '../types/Calling';
import { LocalizerType } from '../types/Util';
+import { UUID } from '../types/UUID';
import { ConversationModel } from '../models/conversations';
import * as Bytes from '../Bytes';
import {
@@ -60,7 +61,6 @@ import {
arrayBufferToUuid,
typedArrayToArrayBuffer,
} from '../Crypto';
-import { assert } from '../util/assert';
import { dropNull, shallowDropNull } from '../util/dropNull';
import { getOwn } from '../util/getOwn';
import * as durations from '../util/durations';
@@ -262,7 +262,7 @@ export class CallingClass {
}
private attemptToGiveOurUuidToRingRtc(): void {
- const ourUuid = window.textsecure.storage.user.getUuid();
+ const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
if (!ourUuid) {
// This can happen if we're not linked. It's okay if we hit this case.
return;
@@ -1392,15 +1392,17 @@ export class CallingClass {
return;
}
- const remoteUserId = envelope.sourceUuid || envelope.source;
+ const remoteUserId = envelope.sourceUuid;
const remoteDeviceId = this.parseDeviceId(envelope.sourceDevice);
if (!remoteUserId || !remoteDeviceId || !this.localDeviceId) {
window.log.error('Missing identifier, ignoring call message.');
return;
}
- const senderIdentityRecord = window.textsecure.storage.protocol.getIdentityRecord(
- remoteUserId
+ const { storage } = window.textsecure;
+
+ const senderIdentityRecord = await storage.protocol.getOrMigrateIdentityRecord(
+ new UUID(remoteUserId)
);
if (!senderIdentityRecord) {
window.log.error(
@@ -1410,14 +1412,9 @@ export class CallingClass {
}
const senderIdentityKey = senderIdentityRecord.publicKey.slice(1); // Ignore the type header, it is not used.
- const ourIdentifier =
- window.textsecure.storage.user.getUuid() ||
- window.textsecure.storage.user.getNumber();
- assert(ourIdentifier, 'We should have either uuid or number');
+ const ourUuid = storage.user.getCheckedUuid();
- const receiverIdentityRecord = window.textsecure.storage.protocol.getIdentityRecord(
- ourIdentifier
- );
+ const receiverIdentityRecord = storage.protocol.getIdentityRecord(ourUuid);
if (!receiverIdentityRecord) {
window.log.error(
'Missing receiver identity record; ignoring call message.'
diff --git a/ts/services/groupCredentialFetcher.ts b/ts/services/groupCredentialFetcher.ts
index 5942cc8a41..6164bcbd36 100644
--- a/ts/services/groupCredentialFetcher.ts
+++ b/ts/services/groupCredentialFetcher.ts
@@ -115,7 +115,7 @@ export function getCredentialsForToday(
}
export async function maybeFetchNewCredentials(): Promise {
- const uuid = window.textsecure.storage.user.getUuid();
+ const uuid = window.textsecure.storage.user.getUuid()?.toString();
if (!uuid) {
window.log.info('maybeFetchCredentials: no UUID, returning early');
return;
diff --git a/ts/services/storage.ts b/ts/services/storage.ts
index 53887466a1..b571267b72 100644
--- a/ts/services/storage.ts
+++ b/ts/services/storage.ts
@@ -166,6 +166,11 @@ async function generateManifest(
storageRecord.account = await toAccountRecord(conversation);
identifier.type = ITEM_TYPE.ACCOUNT;
} else if (conversationType === ConversationTypes.Direct) {
+ // Contacts must have UUID
+ if (!conversation.get('uuid')) {
+ continue;
+ }
+
storageRecord = new Proto.StorageRecord();
// eslint-disable-next-line no-await-in-loop
storageRecord.contact = await toContactRecord(conversation);
diff --git a/ts/services/storageRecordOps.ts b/ts/services/storageRecordOps.ts
index 64d3e2f5fd..6771c0d99c 100644
--- a/ts/services/storageRecordOps.ts
+++ b/ts/services/storageRecordOps.ts
@@ -35,6 +35,8 @@ import {
} from '../util/universalExpireTimer';
import { ourProfileKeyService } from './ourProfileKey';
import { isGroupV1, isGroupV2 } from '../util/whatTypeOfConversation';
+import { UUID } from '../types/UUID';
+import * as Errors from '../types/errors';
import { SignalService as Proto } from '../protobuf';
const { updateConversation } = dataInterface;
@@ -118,9 +120,19 @@ export async function toContactRecord(
if (profileKey) {
contactRecord.profileKey = Bytes.fromBase64(String(profileKey));
}
- const identityKey = await window.textsecure.storage.protocol.loadIdentityKey(
- conversation.id
- );
+
+ let maybeUuid: UUID | undefined;
+ try {
+ maybeUuid = uuid ? new UUID(uuid) : undefined;
+ } catch (error) {
+ window.log.warn(
+ `Invalid uuid in contact record: ${Errors.toLogFormat(error)}`
+ );
+ }
+
+ const identityKey = maybeUuid
+ ? await window.textsecure.storage.protocol.loadIdentityKey(maybeUuid)
+ : undefined;
if (identityKey) {
contactRecord.identityKey = new FIXMEU8(identityKey);
}
@@ -723,6 +735,11 @@ export async function mergeContactRecord(
const e164 = contactRecord.serviceE164 || undefined;
const uuid = contactRecord.serviceUuid || undefined;
+ // All contacts must have UUID
+ if (!uuid) {
+ return false;
+ }
+
const id = window.ConversationController.ensureContactIds({
e164,
uuid,
diff --git a/ts/sql/Client.ts b/ts/sql/Client.ts
index b31780366c..b0e2f60fe8 100644
--- a/ts/sql/Client.ts
+++ b/ts/sql/Client.ts
@@ -50,14 +50,17 @@ import {
ConversationType,
DeleteSentProtoRecipientOptionsType,
IdentityKeyType,
+ IdentityKeyIdType,
ItemKeyType,
ItemType,
LastConversationMessagesType,
MessageType,
MessageTypeUnhydrated,
PreKeyType,
+ PreKeyIdType,
SearchResultMessageType,
SenderKeyType,
+ SenderKeyIdType,
SentMessageDBType,
SentMessagesType,
SentProtoType,
@@ -66,7 +69,9 @@ import {
SentRecipientsType,
ServerInterface,
SessionType,
+ SessionIdType,
SignedPreKeyType,
+ SignedPreKeyIdType,
StickerPackStatusType,
StickerPackType,
StickerType,
@@ -634,17 +639,10 @@ async function removeIndexedDBFiles() {
const IDENTITY_KEY_KEYS = ['publicKey'];
async function createOrUpdateIdentityKey(data: IdentityKeyType) {
- const updated = keysFromArrayBuffer(IDENTITY_KEY_KEYS, {
- ...data,
- id: window.ConversationController.getConversationId(data.id),
- });
+ const updated = keysFromArrayBuffer(IDENTITY_KEY_KEYS, data);
await channels.createOrUpdateIdentityKey(updated);
}
-async function getIdentityKeyById(identifier: string) {
- const id = window.ConversationController.getConversationId(identifier);
- if (!id) {
- throw new Error('getIdentityKeyById: unable to find conversationId');
- }
+async function getIdentityKeyById(id: IdentityKeyIdType) {
const data = await channels.getIdentityKeyById(id);
return keysToArrayBuffer(IDENTITY_KEY_KEYS, data);
@@ -655,11 +653,7 @@ async function bulkAddIdentityKeys(array: Array) {
);
await channels.bulkAddIdentityKeys(updated);
}
-async function removeIdentityKeyById(identifier: string) {
- const id = window.ConversationController.getConversationId(identifier);
- if (!id) {
- throw new Error('removeIdentityKeyById: unable to find conversationId');
- }
+async function removeIdentityKeyById(id: IdentityKeyIdType) {
await channels.removeIdentityKeyById(id);
}
async function removeAllIdentityKeys() {
@@ -677,7 +671,7 @@ async function createOrUpdatePreKey(data: PreKeyType) {
const updated = keysFromArrayBuffer(PRE_KEY_KEYS, data);
await channels.createOrUpdatePreKey(updated);
}
-async function getPreKeyById(id: number) {
+async function getPreKeyById(id: PreKeyIdType) {
const data = await channels.getPreKeyById(id);
return keysToArrayBuffer(PRE_KEY_KEYS, data);
@@ -686,7 +680,7 @@ async function bulkAddPreKeys(array: Array) {
const updated = map(array, data => keysFromArrayBuffer(PRE_KEY_KEYS, data));
await channels.bulkAddPreKeys(updated);
}
-async function removePreKeyById(id: number) {
+async function removePreKeyById(id: PreKeyIdType) {
await channels.removePreKeyById(id);
}
async function removeAllPreKeys() {
@@ -705,7 +699,7 @@ async function createOrUpdateSignedPreKey(data: SignedPreKeyType) {
const updated = keysFromArrayBuffer(PRE_KEY_KEYS, data);
await channels.createOrUpdateSignedPreKey(updated);
}
-async function getSignedPreKeyById(id: number) {
+async function getSignedPreKeyById(id: SignedPreKeyIdType) {
const data = await channels.getSignedPreKeyById(id);
return keysToArrayBuffer(PRE_KEY_KEYS, data);
@@ -721,7 +715,7 @@ async function bulkAddSignedPreKeys(array: Array) {
const updated = map(array, data => keysFromArrayBuffer(PRE_KEY_KEYS, data));
await channels.bulkAddSignedPreKeys(updated);
}
-async function removeSignedPreKeyById(id: number) {
+async function removeSignedPreKeyById(id: SignedPreKeyIdType) {
await channels.removeSignedPreKeyById(id);
}
async function removeAllSignedPreKeys() {
@@ -731,7 +725,6 @@ async function removeAllSignedPreKeys() {
// Items
const ITEM_KEYS: Partial>> = {
- identityKey: ['value.pubKey', 'value.privKey'],
senderCertificate: ['value.serialized'],
senderCertificateNoE164: ['value.serialized'],
profileKey: ['value'],
@@ -749,7 +742,9 @@ async function createOrUpdateItem(data: ItemType) {
await channels.createOrUpdateItem(updated);
}
-async function getItemById(id: K): Promise> {
+async function getItemById(
+ id: K
+): Promise | undefined> {
const keys = ITEM_KEYS[id];
const data = await channels.getItemById(id);
@@ -788,7 +783,7 @@ async function createOrUpdateSenderKey(key: SenderKeyType): Promise {
await channels.createOrUpdateSenderKey(key);
}
async function getSenderKeyById(
- id: string
+ id: SenderKeyIdType
): Promise {
return channels.getSenderKeyById(id);
}
@@ -798,7 +793,7 @@ async function removeAllSenderKeys(): Promise {
async function getAllSenderKeys(): Promise> {
return channels.getAllSenderKeys();
}
-async function removeSenderKeyById(id: string): Promise {
+async function removeSenderKeyById(id: SenderKeyIdType): Promise {
return channels.removeSenderKeyById(id);
}
@@ -879,7 +874,7 @@ async function commitSessionsAndUnprocessed(options: {
async function bulkAddSessions(array: Array) {
await channels.bulkAddSessions(array);
}
-async function removeSessionById(id: string) {
+async function removeSessionById(id: SessionIdType) {
await channels.removeSessionById(id);
}
diff --git a/ts/sql/Interface.ts b/ts/sql/Interface.ts
index 7343f9c3a3..3fa9dffe41 100644
--- a/ts/sql/Interface.ts
+++ b/ts/sql/Interface.ts
@@ -19,6 +19,8 @@ import type { ProcessGroupCallRingRequestResult } from '../types/Calling';
import { StorageAccessType } from '../types/Storage.d';
import type { AttachmentType } from '../types/Attachment';
import { BodyRangesType } from '../types/Util';
+import type { QualifiedAddressStringType } from '../types/QualifiedAddress';
+import type { UUIDStringType } from '../types/UUID';
import type { RemoveAllConfiguration } from '../types/RemoveAllConfiguration';
export type AttachmentDownloadJobTypeType =
@@ -57,14 +59,17 @@ export type EmojiType = {
shortName: string;
lastUsage: number;
};
+
export type IdentityKeyType = {
firstUse: boolean;
- id: string;
+ id: UUIDStringType | `conversation:${UUIDStringType}`;
nonblockingApproval: boolean;
publicKey: ArrayBuffer;
timestamp: number;
verified: number;
};
+export type IdentityKeyIdType = IdentityKeyType['id'];
+
export type ItemKeyType = keyof StorageAccessType;
export type AllItemsType = Partial;
export type ItemType = {
@@ -76,10 +81,13 @@ export type MessageTypeUnhydrated = {
json: string;
};
export type PreKeyType = {
- id: number;
+ id: `${UUIDStringType}:${number}`;
+ keyId: number;
+ ourUuid: UUIDStringType;
privateKey: ArrayBuffer;
publicKey: ArrayBuffer;
};
+export type PreKeyIdType = PreKeyType['id'];
export type SearchResultMessageType = {
json: string;
snippet: string;
@@ -114,7 +122,7 @@ export type SentMessageDBType = {
export type SenderKeyType = {
// Primary key
- id: string;
+ id: `${QualifiedAddressStringType}--${string}`;
// These two are combined into one string to give us the final id
senderId: string;
distributionId: string;
@@ -122,21 +130,28 @@ export type SenderKeyType = {
data: Buffer;
lastUpdatedDate: number;
};
+export type SenderKeyIdType = SenderKeyType['id'];
export type SessionType = {
- id: string;
+ id: QualifiedAddressStringType;
+ ourUuid: UUIDStringType;
+ uuid: UUIDStringType;
conversationId: string;
deviceId: number;
record: string;
version?: number;
};
+export type SessionIdType = SessionType['id'];
export type SignedPreKeyType = {
confirmed: boolean;
// eslint-disable-next-line camelcase
created_at: number;
- id: number;
+ ourUuid: UUIDStringType;
+ id: `${UUIDStringType}:${number}`;
+ keyId: number;
privateKey: ArrayBuffer;
publicKey: ArrayBuffer;
};
+export type SignedPreKeyIdType = SignedPreKeyType['id'];
export type StickerType = Readonly<{
id: number;
@@ -227,23 +242,27 @@ export type DataInterface = {
removeIndexedDBFiles: () => Promise;
createOrUpdateIdentityKey: (data: IdentityKeyType) => Promise;
- getIdentityKeyById: (id: string) => Promise;
+ getIdentityKeyById: (
+ id: IdentityKeyIdType
+ ) => Promise;
bulkAddIdentityKeys: (array: Array) => Promise;
- removeIdentityKeyById: (id: string) => Promise;
+ removeIdentityKeyById: (id: IdentityKeyIdType) => Promise;
removeAllIdentityKeys: () => Promise;
getAllIdentityKeys: () => Promise>;
createOrUpdatePreKey: (data: PreKeyType) => Promise;
- getPreKeyById: (id: number) => Promise;
+ getPreKeyById: (id: PreKeyIdType) => Promise;
bulkAddPreKeys: (array: Array) => Promise;
- removePreKeyById: (id: number) => Promise;
+ removePreKeyById: (id: PreKeyIdType) => Promise;
removeAllPreKeys: () => Promise;
getAllPreKeys: () => Promise>;
createOrUpdateSignedPreKey: (data: SignedPreKeyType) => Promise;
- getSignedPreKeyById: (id: number) => Promise;
+ getSignedPreKeyById: (
+ id: SignedPreKeyIdType
+ ) => Promise;
bulkAddSignedPreKeys: (array: Array) => Promise;
- removeSignedPreKeyById: (id: number) => Promise;
+ removeSignedPreKeyById: (id: SignedPreKeyIdType) => Promise;
removeAllSignedPreKeys: () => Promise;
getAllSignedPreKeys: () => Promise>;
@@ -254,10 +273,10 @@ export type DataInterface = {
getAllItems: () => Promise;
createOrUpdateSenderKey: (key: SenderKeyType) => Promise;
- getSenderKeyById: (id: string) => Promise;
+ getSenderKeyById: (id: SenderKeyIdType) => Promise;
removeAllSenderKeys: () => Promise;
getAllSenderKeys: () => Promise>;
- removeSenderKeyById: (id: string) => Promise;
+ removeSenderKeyById: (id: SenderKeyIdType) => Promise;
insertSentProto: (
proto: SentProtoType,
@@ -296,7 +315,7 @@ export type DataInterface = {
unprocessed: Array;
}): Promise;
bulkAddSessions: (array: Array) => Promise;
- removeSessionById: (id: string) => Promise;
+ removeSessionById: (id: SessionIdType) => Promise;
removeSessionsByConversation: (conversationId: string) => Promise;
removeAllSessions: () => Promise;
getAllSessions: () => Promise>;
diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts
index 6e03ba0110..4e43f97f7e 100644
--- a/ts/sql/Server.ts
+++ b/ts/sql/Server.ts
@@ -30,6 +30,7 @@ import {
} from 'lodash';
import { ReadStatus } from '../messages/MessageReadStatus';
+import Helpers from '../textsecure/Helpers';
import { GroupV2MemberType } from '../model-types.d';
import { ReactionType } from '../types/Reactions';
import { STORAGE_UI_KEYS } from '../types/StorageUIKeys';
@@ -40,6 +41,7 @@ import { dropNull } from '../util/dropNull';
import { isNormalNumber } from '../util/isNormalNumber';
import { isNotNil } from '../util/isNotNil';
import { missingCaseError } from '../util/missingCaseError';
+import { isValidGuid } from '../util/isValidGuid';
import { parseIntOrThrow } from '../util/parseIntOrThrow';
import * as durations from '../util/durations';
import { formatCountForLogging } from '../logging/formatCountForLogging';
@@ -55,6 +57,7 @@ import {
DeleteSentProtoRecipientOptionsType,
EmojiType,
IdentityKeyType,
+ IdentityKeyIdType,
ItemKeyType,
ItemType,
LastConversationMessagesServerType,
@@ -62,8 +65,10 @@ import {
MessageType,
MessageTypeUnhydrated,
PreKeyType,
+ PreKeyIdType,
SearchResultMessageType,
SenderKeyType,
+ SenderKeyIdType,
SentMessageDBType,
SentMessagesType,
SentProtoType,
@@ -72,7 +77,9 @@ import {
SentRecipientsType,
ServerInterface,
SessionType,
+ SessionIdType,
SignedPreKeyType,
+ SignedPreKeyIdType,
StickerPackStatusType,
StickerPackType,
StickerType,
@@ -2101,6 +2108,336 @@ function updateToSchemaVersion40(currentVersion: number, db: Database) {
console.log('updateToSchemaVersion40: success!');
}
+function updateToSchemaVersion41(currentVersion: number, db: Database) {
+ if (currentVersion >= 41) {
+ return;
+ }
+
+ const getConversationUuid = db.prepare(
+ `
+ SELECT uuid
+ FROM
+ conversations
+ WHERE
+ id = $conversationId
+ `
+ );
+
+ const clearSessionsAndKeys = () => {
+ // ts/background.ts will ask user to relink so all that matters here is
+ // to maintain an invariant:
+ //
+ // After this migration all sessions and keys are prefixed by
+ // "uuid:".
+ db.exec(
+ `
+ DELETE FROM senderKeys;
+ DELETE FROM sessions;
+ DELETE FROM signedPreKeys;
+ DELETE FROM preKeys;
+ `
+ );
+
+ assertSync(removeById('items', 'identityKey', db));
+ assertSync(removeById('items', 'registrationId', db));
+ };
+
+ const moveIdentityKeyToMap = (ourUuid: string) => {
+ type IdentityKeyType = {
+ privKey: string;
+ publicKey: string;
+ };
+
+ const identityKey = assertSync(
+ getById('items', 'identityKey', db)
+ );
+
+ type RegistrationId = number;
+
+ const registrationId = assertSync(
+ getById('items', 'registrationId', db)
+ );
+
+ if (identityKey) {
+ assertSync(
+ createOrUpdateSync(
+ 'items',
+ {
+ id: 'identityKeyMap',
+ value: {
+ [ourUuid]: identityKey.value,
+ },
+ },
+ db
+ )
+ );
+ }
+
+ if (registrationId) {
+ assertSync(
+ createOrUpdateSync(
+ 'items',
+ {
+ id: 'registrationIdMap',
+ value: {
+ [ourUuid]: registrationId.value,
+ },
+ },
+ db
+ )
+ );
+ }
+
+ assertSync(removeById('items', 'identityKey', db));
+ assertSync(removeById('items', 'registrationId', db));
+ };
+
+ const prefixKeys = (ourUuid: string) => {
+ for (const table of ['signedPreKeys', 'preKeys']) {
+ // Add numeric `keyId` field to keys
+ db.prepare(
+ `
+ UPDATE ${table}
+ SET
+ json = json_insert(
+ json,
+ '$.keyId',
+ json_extract(json, '$.id')
+ )
+ `
+ ).run();
+
+ // Update id to include suffix and add `ourUuid` field
+ db.prepare(
+ `
+ UPDATE ${table}
+ SET
+ id = $ourUuid || ':' || id,
+ json = json_set(
+ json,
+ '$.id',
+ $ourUuid || ':' || json_extract(json, '$.id'),
+ '$.ourUuid',
+ $ourUuid
+ )
+ `
+ ).run({ ourUuid });
+ }
+
+ const senderKeys: ReadonlyArray<{
+ id: string;
+ senderId: string;
+ }> = db.prepare('SELECT id, senderId FROM senderKeys').all();
+
+ console.log(`Updating ${senderKeys.length} sender keys`);
+
+ const updateSenderKey = db.prepare(
+ `
+ UPDATE senderKeys
+ SET
+ id = $newId,
+ senderId = $newSenderId
+ WHERE
+ id = $id
+ `
+ );
+
+ const deleteSenderKey = db.prepare(
+ 'DELETE FROM senderKeys WHERE id = $id'
+ );
+
+ let updated = 0;
+ let deleted = 0;
+ for (const { id, senderId } of senderKeys) {
+ const [conversationId] = Helpers.unencodeNumber(senderId);
+ const { uuid } = getConversationUuid.get({ conversationId });
+
+ if (!uuid) {
+ deleted += 1;
+ deleteSenderKey.run({ id });
+ continue;
+ }
+
+ updated += 1;
+ updateSenderKey.run({
+ id,
+ newId: `${ourUuid}:${id.replace(conversationId, uuid)}`,
+ newSenderId: `${senderId.replace(conversationId, uuid)}`,
+ });
+ }
+
+ console.log(
+ `Updated ${senderKeys.length} sender keys: ` +
+ `updated: ${updated}, deleted: ${deleted}`
+ );
+ };
+
+ const updateSessions = (ourUuid: string) => {
+ // Use uuid instead of conversation id in existing sesions and prefix id
+ // with ourUuid.
+ //
+ // Set ourUuid column and field in json
+ const allSessions = db
+ .prepare('SELECT id, conversationId FROM SESSIONS')
+ .all();
+
+ console.log(`Updating ${allSessions.length} sessions`);
+
+ const updateSession = db.prepare(
+ `
+ UPDATE sessions
+ SET
+ id = $newId,
+ ourUuid = $ourUuid,
+ uuid = $uuid,
+ json = json_set(
+ sessions.json,
+ '$.id',
+ $newId,
+ '$.uuid',
+ $uuid,
+ '$.ourUuid',
+ $ourUuid
+ )
+ WHERE
+ id = $id
+ `
+ );
+
+ const deleteSession = db.prepare(
+ 'DELETE FROM sessions WHERE id = $id'
+ );
+
+ let updated = 0;
+ let deleted = 0;
+ for (const { id, conversationId } of allSessions) {
+ const { uuid } = getConversationUuid.get({ conversationId });
+ if (!uuid) {
+ deleted += 1;
+ deleteSession.run({ id });
+ continue;
+ }
+
+ const newId = `${ourUuid}:${id.replace(conversationId, uuid)}`;
+
+ updated += 1;
+ updateSession.run({
+ id,
+ newId,
+ uuid,
+ ourUuid,
+ });
+ }
+
+ console.log(
+ `Updated ${allSessions.length} sessions: ` +
+ `updated: ${updated}, deleted: ${deleted}`
+ );
+ };
+
+ const updateIdentityKeys = () => {
+ const identityKeys: ReadonlyArray<{
+ id: string;
+ }> = db.prepare('SELECT id FROM identityKeys').all();
+
+ console.log(`Updating ${identityKeys.length} identity keys`);
+
+ const updateIdentityKey = db.prepare(
+ `
+ UPDATE identityKeys
+ SET
+ id = $newId,
+ json = json_set(
+ identityKeys.json,
+ '$.id',
+ $newId
+ )
+ WHERE
+ id = $id
+ `
+ );
+
+ let migrated = 0;
+ for (const { id } of identityKeys) {
+ const { uuid } = getConversationUuid.get({ conversationId: id });
+
+ let newId: string;
+ if (uuid) {
+ migrated += 1;
+ newId = uuid;
+ } else {
+ newId = `conversation:${id}`;
+ }
+
+ updateIdentityKey.run({ id, newId });
+ }
+
+ console.log(`Migrated ${migrated} identity keys`);
+ };
+
+ db.transaction(() => {
+ db.exec(
+ `
+ -- Change type of 'id' column from INTEGER to STRING
+
+ ALTER TABLE preKeys
+ RENAME TO old_preKeys;
+
+ ALTER TABLE signedPreKeys
+ RENAME TO old_signedPreKeys;
+
+ CREATE TABLE preKeys(
+ id STRING PRIMARY KEY ASC,
+ json TEXT
+ );
+ CREATE TABLE signedPreKeys(
+ id STRING PRIMARY KEY ASC,
+ json TEXT
+ );
+
+ -- sqlite handles the type conversion
+ INSERT INTO preKeys SELECT * FROM old_preKeys;
+ INSERT INTO signedPreKeys SELECT * FROM old_signedPreKeys;
+
+ DROP TABLE old_preKeys;
+ DROP TABLE old_signedPreKeys;
+
+ -- Alter sessions
+
+ ALTER TABLE sessions
+ ADD COLUMN ourUuid STRING;
+
+ ALTER TABLE sessions
+ ADD COLUMN uuid STRING;
+ `
+ );
+
+ const ourUuid = getOurUuid(db);
+
+ if (!isValidGuid(ourUuid)) {
+ console.error(
+ 'updateToSchemaVersion41: no uuid is available clearing sessions'
+ );
+
+ clearSessionsAndKeys();
+
+ db.pragma('user_version = 41');
+ return;
+ }
+
+ prefixKeys(ourUuid);
+
+ updateSessions(ourUuid);
+
+ moveIdentityKeyToMap(ourUuid);
+
+ updateIdentityKeys();
+
+ db.pragma('user_version = 41');
+ })();
+ console.log('updateToSchemaVersion41: success!');
+}
+
const SCHEMA_VERSIONS = [
updateToSchemaVersion1,
updateToSchemaVersion2,
@@ -2142,6 +2479,7 @@ const SCHEMA_VERSIONS = [
updateToSchemaVersion38,
updateToSchemaVersion39,
updateToSchemaVersion40,
+ updateToSchemaVersion41,
];
function updateSchema(db: Database): void {
@@ -2173,6 +2511,23 @@ function updateSchema(db: Database): void {
}
}
+function getOurUuid(db: Database): string | undefined {
+ const UUID_ID: ItemKeyType = 'uuid_id';
+
+ const row: { json: string } | undefined = db
+ .prepare('SELECT json FROM items WHERE id = $id;')
+ .get({ id: UUID_ID });
+
+ if (!row) {
+ return undefined;
+ }
+
+ const { value } = JSON.parse(row.json);
+
+ const [ourUuid] = Helpers.unencodeNumber(String(value).toLowerCase());
+ return ourUuid;
+}
+
let globalInstance: Database | undefined;
let globalInstanceRenderer: Database | undefined;
let databaseFilePath: string | undefined;
@@ -2370,13 +2725,15 @@ const IDENTITY_KEYS_TABLE = 'identityKeys';
function createOrUpdateIdentityKey(data: IdentityKeyType): Promise {
return createOrUpdate(IDENTITY_KEYS_TABLE, data);
}
-function getIdentityKeyById(id: string): Promise {
+async function getIdentityKeyById(
+ id: IdentityKeyIdType
+): Promise {
return getById(IDENTITY_KEYS_TABLE, id);
}
function bulkAddIdentityKeys(array: Array): Promise {
return bulkAdd(IDENTITY_KEYS_TABLE, array);
}
-function removeIdentityKeyById(id: string): Promise {
+async function removeIdentityKeyById(id: IdentityKeyIdType): Promise {
return removeById(IDENTITY_KEYS_TABLE, id);
}
function removeAllIdentityKeys(): Promise {
@@ -2390,13 +2747,15 @@ const PRE_KEYS_TABLE = 'preKeys';
function createOrUpdatePreKey(data: PreKeyType): Promise {
return createOrUpdate(PRE_KEYS_TABLE, data);
}
-function getPreKeyById(id: number): Promise {
+async function getPreKeyById(
+ id: PreKeyIdType
+): Promise {
return getById(PRE_KEYS_TABLE, id);
}
function bulkAddPreKeys(array: Array): Promise {
return bulkAdd(PRE_KEYS_TABLE, array);
}
-function removePreKeyById(id: number): Promise {
+async function removePreKeyById(id: PreKeyIdType): Promise {
return removeById(PRE_KEYS_TABLE, id);
}
function removeAllPreKeys(): Promise {
@@ -2410,15 +2769,15 @@ const SIGNED_PRE_KEYS_TABLE = 'signedPreKeys';
function createOrUpdateSignedPreKey(data: SignedPreKeyType): Promise {
return createOrUpdate(SIGNED_PRE_KEYS_TABLE, data);
}
-function getSignedPreKeyById(
- id: number
+async function getSignedPreKeyById(
+ id: SignedPreKeyIdType
): Promise {
return getById(SIGNED_PRE_KEYS_TABLE, id);
}
function bulkAddSignedPreKeys(array: Array): Promise {
return bulkAdd(SIGNED_PRE_KEYS_TABLE, array);
}
-function removeSignedPreKeyById(id: number): Promise {
+async function removeSignedPreKeyById(id: SignedPreKeyIdType): Promise {
return removeById(SIGNED_PRE_KEYS_TABLE, id);
}
function removeAllSignedPreKeys(): Promise {
@@ -2445,7 +2804,7 @@ function createOrUpdateItem(
): Promise {
return createOrUpdate(ITEMS_TABLE, data);
}
-function getItemById(
+async function getItemById(
id: K
): Promise | undefined> {
return getById(ITEMS_TABLE, id);
@@ -2467,7 +2826,7 @@ async function getAllItems(): Promise {
return result;
}
-function removeItemById(id: ItemKeyType): Promise {
+async function removeItemById(id: ItemKeyType): Promise {
return removeById(ITEMS_TABLE, id);
}
function removeAllItems(): Promise {
@@ -2497,7 +2856,7 @@ async function createOrUpdateSenderKey(key: SenderKeyType): Promise {
).run(key);
}
async function getSenderKeyById(
- id: string
+ id: SenderKeyIdType
): Promise {
const db = getInstance();
const row = prepare(db, 'SELECT * FROM senderKeys WHERE id = $id').get({
@@ -2516,7 +2875,7 @@ async function getAllSenderKeys(): Promise> {
return rows;
}
-async function removeSenderKeyById(id: string): Promise {
+async function removeSenderKeyById(id: SenderKeyIdType): Promise {
const db = getInstance();
prepare(db, 'DELETE FROM senderKeys WHERE id = $id').run({ id });
}
@@ -2840,7 +3199,7 @@ async function _getAllSentProtoMessageIds(): Promise> {
const SESSIONS_TABLE = 'sessions';
function createOrUpdateSessionSync(data: SessionType): void {
const db = getInstance();
- const { id, conversationId } = data;
+ const { id, conversationId, ourUuid, uuid } = data;
if (!id) {
throw new Error(
'createOrUpdateSession: Provided data did not have a truthy id'
@@ -2858,16 +3217,22 @@ function createOrUpdateSessionSync(data: SessionType): void {
INSERT OR REPLACE INTO sessions (
id,
conversationId,
+ ourUuid,
+ uuid,
json
) values (
$id,
$conversationId,
+ $ourUuid,
+ $uuid,
$json
)
`
).run({
id,
conversationId,
+ ourUuid,
+ uuid,
json: objectToJSON(data),
});
}
@@ -2910,7 +3275,7 @@ async function commitSessionsAndUnprocessed({
function bulkAddSessions(array: Array): Promise {
return bulkAdd(SESSIONS_TABLE, array);
}
-function removeSessionById(id: string): Promise {
+async function removeSessionById(id: SessionIdType): Promise {
return removeById(SESSIONS_TABLE, id);
}
async function removeSessionsByConversation(
@@ -2933,11 +3298,11 @@ function getAllSessions(): Promise