UUID-keyed lookups in SignalProtocolStore
This commit is contained in:
parent
6323aedd9b
commit
c7e7d55af4
46 changed files with 2094 additions and 1447 deletions
|
@ -309,7 +309,6 @@
|
|||
type="text/javascript"
|
||||
src="js/rotate_signed_prekey_listener.js"
|
||||
></script>
|
||||
<script type="text/javascript" src="js/keychange_listener.js"></script>
|
||||
</head>
|
||||
<body class="overflow-hidden">
|
||||
<div id="app-container">
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -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 = {
|
||||
|
|
|
@ -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: <ArrayBuffer>
|
||||
* }
|
||||
*/
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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(),
|
||||
};
|
|
@ -14,10 +14,6 @@
|
|||
<script type="text/javascript" src="fake_web_api.js"></script>
|
||||
|
||||
<script type="text/javascript" src="test.js"></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="in_memory_signal_protocol_store.js"
|
||||
></script>
|
||||
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
@ -36,7 +32,6 @@
|
|||
></script>
|
||||
|
||||
<script type="text/javascript" src="helpers_test.js"></script>
|
||||
<script type="text/javascript" src="generate_keys_test.js"></script>
|
||||
<script type="text/javascript" src="task_with_timeout_test.js"></script>
|
||||
<script type="text/javascript" src="account_manager_test.js"></script>
|
||||
<script type="text/javascript" src="sendmessage_test.js"></script>
|
||||
|
|
|
@ -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, []);
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -248,11 +248,6 @@
|
|||
></script>
|
||||
|
||||
<script type="text/javascript" src="../js/libphonenumber-util.js"></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="../js/keychange_listener.js"
|
||||
data-cover
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="../js/expiring_messages.js"
|
||||
|
@ -313,7 +308,6 @@
|
|||
<script type="text/javascript" src="views/list_view_test.js"></script>
|
||||
|
||||
<script type="text/javascript" src="libphonenumber_util_test.js"></script>
|
||||
<script type="text/javascript" src="keychange_listener_test.js"></script>
|
||||
<script type="text/javascript" src="reliable_trigger_test.js"></script>
|
||||
<script type="text/javascript" src="database_test.js"></script>
|
||||
<script type="text/javascript" src="i18n_test.js"></script>
|
||||
|
|
|
@ -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<ConversationModel>
|
||||
attributes: Partial<ConversationAttributesType>
|
||||
): 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'
|
||||
|
|
|
@ -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<void> {
|
||||
await window.textsecure.storage.protocol.storeSession(
|
||||
encodedNameFromAddress(address),
|
||||
toQualifiedAddress(this.ourUuid, address),
|
||||
record,
|
||||
{ zone: this.zone }
|
||||
);
|
||||
}
|
||||
|
||||
async getSession(name: ProtocolAddress): Promise<SessionRecord | null> {
|
||||
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<ProtocolAddress>
|
||||
): Promise<Array<SessionRecord>> {
|
||||
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<PrivateKey> {
|
||||
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<number> {
|
||||
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<PublicKey | null> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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<void> {
|
||||
await window.textsecure.storage.protocol.storePreKey(
|
||||
this.ourUuid,
|
||||
id,
|
||||
freezePreKey(record)
|
||||
);
|
||||
}
|
||||
|
||||
async getPreKey(id: number): Promise<PreKeyRecord> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<SenderKeyRecord | null> {
|
||||
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<void> {
|
||||
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<SignedPreKeyRecord> {
|
||||
const signedPreKey = await window.textsecure.storage.protocol.loadSignedPreKey(
|
||||
this.ourUuid,
|
||||
id
|
||||
);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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<void> {
|
|||
|
||||
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<void> {
|
|||
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<void> {
|
|||
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<void> {
|
|||
|
||||
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<void> {
|
|||
(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<void> {
|
|||
});
|
||||
|
||||
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<void> {
|
|||
|
||||
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<void> {
|
|||
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<void> {
|
|||
|
||||
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;
|
||||
|
|
35
ts/groups.ts
35
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,
|
||||
|
|
|
@ -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<number> {
|
||||
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<CallbackResultType | void> {
|
||||
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<void> {
|
||||
async addKeyChange(keyChangedId: UUID): Promise<void> {
|
||||
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
|
||||
);
|
||||
|
|
|
@ -221,7 +221,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
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<MessageAttributesType> {
|
|||
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)) {
|
||||
|
|
|
@ -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.'
|
||||
|
|
|
@ -115,7 +115,7 @@ export function getCredentialsForToday(
|
|||
}
|
||||
|
||||
export async function maybeFetchNewCredentials(): Promise<void> {
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<IdentityKeyType>) {
|
|||
);
|
||||
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<PreKeyType>) {
|
|||
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<SignedPreKeyType>) {
|
|||
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<Record<ItemKeyType, Array<string>>> = {
|
||||
identityKey: ['value.pubKey', 'value.privKey'],
|
||||
senderCertificate: ['value.serialized'],
|
||||
senderCertificateNoE164: ['value.serialized'],
|
||||
profileKey: ['value'],
|
||||
|
@ -749,7 +742,9 @@ async function createOrUpdateItem<K extends ItemKeyType>(data: ItemType<K>) {
|
|||
|
||||
await channels.createOrUpdateItem(updated);
|
||||
}
|
||||
async function getItemById<K extends ItemKeyType>(id: K): Promise<ItemType<K>> {
|
||||
async function getItemById<K extends ItemKeyType>(
|
||||
id: K
|
||||
): Promise<ItemType<K> | undefined> {
|
||||
const keys = ITEM_KEYS[id];
|
||||
const data = await channels.getItemById(id);
|
||||
|
||||
|
@ -788,7 +783,7 @@ async function createOrUpdateSenderKey(key: SenderKeyType): Promise<void> {
|
|||
await channels.createOrUpdateSenderKey(key);
|
||||
}
|
||||
async function getSenderKeyById(
|
||||
id: string
|
||||
id: SenderKeyIdType
|
||||
): Promise<SenderKeyType | undefined> {
|
||||
return channels.getSenderKeyById(id);
|
||||
}
|
||||
|
@ -798,7 +793,7 @@ async function removeAllSenderKeys(): Promise<void> {
|
|||
async function getAllSenderKeys(): Promise<Array<SenderKeyType>> {
|
||||
return channels.getAllSenderKeys();
|
||||
}
|
||||
async function removeSenderKeyById(id: string): Promise<void> {
|
||||
async function removeSenderKeyById(id: SenderKeyIdType): Promise<void> {
|
||||
return channels.removeSenderKeyById(id);
|
||||
}
|
||||
|
||||
|
@ -879,7 +874,7 @@ async function commitSessionsAndUnprocessed(options: {
|
|||
async function bulkAddSessions(array: Array<SessionType>) {
|
||||
await channels.bulkAddSessions(array);
|
||||
}
|
||||
async function removeSessionById(id: string) {
|
||||
async function removeSessionById(id: SessionIdType) {
|
||||
await channels.removeSessionById(id);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<StorageAccessType>;
|
||||
export type ItemType<K extends ItemKeyType> = {
|
||||
|
@ -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<void>;
|
||||
|
||||
createOrUpdateIdentityKey: (data: IdentityKeyType) => Promise<void>;
|
||||
getIdentityKeyById: (id: string) => Promise<IdentityKeyType | undefined>;
|
||||
getIdentityKeyById: (
|
||||
id: IdentityKeyIdType
|
||||
) => Promise<IdentityKeyType | undefined>;
|
||||
bulkAddIdentityKeys: (array: Array<IdentityKeyType>) => Promise<void>;
|
||||
removeIdentityKeyById: (id: string) => Promise<void>;
|
||||
removeIdentityKeyById: (id: IdentityKeyIdType) => Promise<void>;
|
||||
removeAllIdentityKeys: () => Promise<void>;
|
||||
getAllIdentityKeys: () => Promise<Array<IdentityKeyType>>;
|
||||
|
||||
createOrUpdatePreKey: (data: PreKeyType) => Promise<void>;
|
||||
getPreKeyById: (id: number) => Promise<PreKeyType | undefined>;
|
||||
getPreKeyById: (id: PreKeyIdType) => Promise<PreKeyType | undefined>;
|
||||
bulkAddPreKeys: (array: Array<PreKeyType>) => Promise<void>;
|
||||
removePreKeyById: (id: number) => Promise<void>;
|
||||
removePreKeyById: (id: PreKeyIdType) => Promise<void>;
|
||||
removeAllPreKeys: () => Promise<void>;
|
||||
getAllPreKeys: () => Promise<Array<PreKeyType>>;
|
||||
|
||||
createOrUpdateSignedPreKey: (data: SignedPreKeyType) => Promise<void>;
|
||||
getSignedPreKeyById: (id: number) => Promise<SignedPreKeyType | undefined>;
|
||||
getSignedPreKeyById: (
|
||||
id: SignedPreKeyIdType
|
||||
) => Promise<SignedPreKeyType | undefined>;
|
||||
bulkAddSignedPreKeys: (array: Array<SignedPreKeyType>) => Promise<void>;
|
||||
removeSignedPreKeyById: (id: number) => Promise<void>;
|
||||
removeSignedPreKeyById: (id: SignedPreKeyIdType) => Promise<void>;
|
||||
removeAllSignedPreKeys: () => Promise<void>;
|
||||
getAllSignedPreKeys: () => Promise<Array<SignedPreKeyType>>;
|
||||
|
||||
|
@ -254,10 +273,10 @@ export type DataInterface = {
|
|||
getAllItems: () => Promise<AllItemsType>;
|
||||
|
||||
createOrUpdateSenderKey: (key: SenderKeyType) => Promise<void>;
|
||||
getSenderKeyById: (id: string) => Promise<SenderKeyType | undefined>;
|
||||
getSenderKeyById: (id: SenderKeyIdType) => Promise<SenderKeyType | undefined>;
|
||||
removeAllSenderKeys: () => Promise<void>;
|
||||
getAllSenderKeys: () => Promise<Array<SenderKeyType>>;
|
||||
removeSenderKeyById: (id: string) => Promise<void>;
|
||||
removeSenderKeyById: (id: SenderKeyIdType) => Promise<void>;
|
||||
|
||||
insertSentProto: (
|
||||
proto: SentProtoType,
|
||||
|
@ -296,7 +315,7 @@ export type DataInterface = {
|
|||
unprocessed: Array<UnprocessedType>;
|
||||
}): Promise<void>;
|
||||
bulkAddSessions: (array: Array<SessionType>) => Promise<void>;
|
||||
removeSessionById: (id: string) => Promise<void>;
|
||||
removeSessionById: (id: SessionIdType) => Promise<void>;
|
||||
removeSessionsByConversation: (conversationId: string) => Promise<void>;
|
||||
removeAllSessions: () => Promise<void>;
|
||||
getAllSessions: () => Promise<Array<SessionType>>;
|
||||
|
|
416
ts/sql/Server.ts
416
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<Query>(
|
||||
`
|
||||
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<string>('items', 'identityKey', db));
|
||||
assertSync(removeById<string>('items', 'registrationId', db));
|
||||
};
|
||||
|
||||
const moveIdentityKeyToMap = (ourUuid: string) => {
|
||||
type IdentityKeyType = {
|
||||
privKey: string;
|
||||
publicKey: string;
|
||||
};
|
||||
|
||||
const identityKey = assertSync(
|
||||
getById<string, { value: IdentityKeyType }>('items', 'identityKey', db)
|
||||
);
|
||||
|
||||
type RegistrationId = number;
|
||||
|
||||
const registrationId = assertSync(
|
||||
getById<string, { value: RegistrationId }>('items', 'registrationId', db)
|
||||
);
|
||||
|
||||
if (identityKey) {
|
||||
assertSync(
|
||||
createOrUpdateSync<ItemKeyType>(
|
||||
'items',
|
||||
{
|
||||
id: 'identityKeyMap',
|
||||
value: {
|
||||
[ourUuid]: identityKey.value,
|
||||
},
|
||||
},
|
||||
db
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (registrationId) {
|
||||
assertSync(
|
||||
createOrUpdateSync<ItemKeyType>(
|
||||
'items',
|
||||
{
|
||||
id: 'registrationIdMap',
|
||||
value: {
|
||||
[ourUuid]: registrationId.value,
|
||||
},
|
||||
},
|
||||
db
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
assertSync(removeById<string>('items', 'identityKey', db));
|
||||
assertSync(removeById<string>('items', 'registrationId', db));
|
||||
};
|
||||
|
||||
const prefixKeys = (ourUuid: string) => {
|
||||
for (const table of ['signedPreKeys', 'preKeys']) {
|
||||
// Add numeric `keyId` field to keys
|
||||
db.prepare<EmptyQuery>(
|
||||
`
|
||||
UPDATE ${table}
|
||||
SET
|
||||
json = json_insert(
|
||||
json,
|
||||
'$.keyId',
|
||||
json_extract(json, '$.id')
|
||||
)
|
||||
`
|
||||
).run();
|
||||
|
||||
// Update id to include suffix and add `ourUuid` field
|
||||
db.prepare<Query>(
|
||||
`
|
||||
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<EmptyQuery>('SELECT id, senderId FROM senderKeys').all();
|
||||
|
||||
console.log(`Updating ${senderKeys.length} sender keys`);
|
||||
|
||||
const updateSenderKey = db.prepare<Query>(
|
||||
`
|
||||
UPDATE senderKeys
|
||||
SET
|
||||
id = $newId,
|
||||
senderId = $newSenderId
|
||||
WHERE
|
||||
id = $id
|
||||
`
|
||||
);
|
||||
|
||||
const deleteSenderKey = db.prepare<Query>(
|
||||
'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<EmptyQuery>('SELECT id, conversationId FROM SESSIONS')
|
||||
.all();
|
||||
|
||||
console.log(`Updating ${allSessions.length} sessions`);
|
||||
|
||||
const updateSession = db.prepare<Query>(
|
||||
`
|
||||
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<Query>(
|
||||
'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<EmptyQuery>('SELECT id FROM identityKeys').all();
|
||||
|
||||
console.log(`Updating ${identityKeys.length} identity keys`);
|
||||
|
||||
const updateIdentityKey = db.prepare<Query>(
|
||||
`
|
||||
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<Query>('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<void> {
|
||||
return createOrUpdate(IDENTITY_KEYS_TABLE, data);
|
||||
}
|
||||
function getIdentityKeyById(id: string): Promise<IdentityKeyType | undefined> {
|
||||
async function getIdentityKeyById(
|
||||
id: IdentityKeyIdType
|
||||
): Promise<IdentityKeyType | undefined> {
|
||||
return getById(IDENTITY_KEYS_TABLE, id);
|
||||
}
|
||||
function bulkAddIdentityKeys(array: Array<IdentityKeyType>): Promise<void> {
|
||||
return bulkAdd(IDENTITY_KEYS_TABLE, array);
|
||||
}
|
||||
function removeIdentityKeyById(id: string): Promise<void> {
|
||||
async function removeIdentityKeyById(id: IdentityKeyIdType): Promise<void> {
|
||||
return removeById(IDENTITY_KEYS_TABLE, id);
|
||||
}
|
||||
function removeAllIdentityKeys(): Promise<void> {
|
||||
|
@ -2390,13 +2747,15 @@ const PRE_KEYS_TABLE = 'preKeys';
|
|||
function createOrUpdatePreKey(data: PreKeyType): Promise<void> {
|
||||
return createOrUpdate(PRE_KEYS_TABLE, data);
|
||||
}
|
||||
function getPreKeyById(id: number): Promise<PreKeyType | undefined> {
|
||||
async function getPreKeyById(
|
||||
id: PreKeyIdType
|
||||
): Promise<PreKeyType | undefined> {
|
||||
return getById(PRE_KEYS_TABLE, id);
|
||||
}
|
||||
function bulkAddPreKeys(array: Array<PreKeyType>): Promise<void> {
|
||||
return bulkAdd(PRE_KEYS_TABLE, array);
|
||||
}
|
||||
function removePreKeyById(id: number): Promise<void> {
|
||||
async function removePreKeyById(id: PreKeyIdType): Promise<void> {
|
||||
return removeById(PRE_KEYS_TABLE, id);
|
||||
}
|
||||
function removeAllPreKeys(): Promise<void> {
|
||||
|
@ -2410,15 +2769,15 @@ const SIGNED_PRE_KEYS_TABLE = 'signedPreKeys';
|
|||
function createOrUpdateSignedPreKey(data: SignedPreKeyType): Promise<void> {
|
||||
return createOrUpdate(SIGNED_PRE_KEYS_TABLE, data);
|
||||
}
|
||||
function getSignedPreKeyById(
|
||||
id: number
|
||||
async function getSignedPreKeyById(
|
||||
id: SignedPreKeyIdType
|
||||
): Promise<SignedPreKeyType | undefined> {
|
||||
return getById(SIGNED_PRE_KEYS_TABLE, id);
|
||||
}
|
||||
function bulkAddSignedPreKeys(array: Array<SignedPreKeyType>): Promise<void> {
|
||||
return bulkAdd(SIGNED_PRE_KEYS_TABLE, array);
|
||||
}
|
||||
function removeSignedPreKeyById(id: number): Promise<void> {
|
||||
async function removeSignedPreKeyById(id: SignedPreKeyIdType): Promise<void> {
|
||||
return removeById(SIGNED_PRE_KEYS_TABLE, id);
|
||||
}
|
||||
function removeAllSignedPreKeys(): Promise<void> {
|
||||
|
@ -2445,7 +2804,7 @@ function createOrUpdateItem<K extends ItemKeyType>(
|
|||
): Promise<void> {
|
||||
return createOrUpdate(ITEMS_TABLE, data);
|
||||
}
|
||||
function getItemById<K extends ItemKeyType>(
|
||||
async function getItemById<K extends ItemKeyType>(
|
||||
id: K
|
||||
): Promise<ItemType<K> | undefined> {
|
||||
return getById(ITEMS_TABLE, id);
|
||||
|
@ -2467,7 +2826,7 @@ async function getAllItems(): Promise<AllItemsType> {
|
|||
|
||||
return result;
|
||||
}
|
||||
function removeItemById(id: ItemKeyType): Promise<void> {
|
||||
async function removeItemById(id: ItemKeyType): Promise<void> {
|
||||
return removeById(ITEMS_TABLE, id);
|
||||
}
|
||||
function removeAllItems(): Promise<void> {
|
||||
|
@ -2497,7 +2856,7 @@ async function createOrUpdateSenderKey(key: SenderKeyType): Promise<void> {
|
|||
).run(key);
|
||||
}
|
||||
async function getSenderKeyById(
|
||||
id: string
|
||||
id: SenderKeyIdType
|
||||
): Promise<SenderKeyType | undefined> {
|
||||
const db = getInstance();
|
||||
const row = prepare(db, 'SELECT * FROM senderKeys WHERE id = $id').get({
|
||||
|
@ -2516,7 +2875,7 @@ async function getAllSenderKeys(): Promise<Array<SenderKeyType>> {
|
|||
|
||||
return rows;
|
||||
}
|
||||
async function removeSenderKeyById(id: string): Promise<void> {
|
||||
async function removeSenderKeyById(id: SenderKeyIdType): Promise<void> {
|
||||
const db = getInstance();
|
||||
prepare(db, 'DELETE FROM senderKeys WHERE id = $id').run({ id });
|
||||
}
|
||||
|
@ -2840,7 +3199,7 @@ async function _getAllSentProtoMessageIds(): Promise<Array<SentMessageDBType>> {
|
|||
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<SessionType>): Promise<void> {
|
||||
return bulkAdd(SESSIONS_TABLE, array);
|
||||
}
|
||||
function removeSessionById(id: string): Promise<void> {
|
||||
async function removeSessionById(id: SessionIdType): Promise<void> {
|
||||
return removeById(SESSIONS_TABLE, id);
|
||||
}
|
||||
async function removeSessionsByConversation(
|
||||
|
@ -2933,11 +3298,11 @@ function getAllSessions(): Promise<Array<SessionType>> {
|
|||
return getAllFromTable(SESSIONS_TABLE);
|
||||
}
|
||||
|
||||
function createOrUpdateSync(
|
||||
function createOrUpdateSync<Key extends string | number>(
|
||||
table: string,
|
||||
data: Record<string, unknown> & { id: string | number }
|
||||
data: Record<string, unknown> & { id: Key },
|
||||
db = getInstance()
|
||||
): void {
|
||||
const db = getInstance();
|
||||
const { id } = data;
|
||||
if (!id) {
|
||||
throw new Error('createOrUpdate: Provided data did not have a truthy id');
|
||||
|
@ -2979,11 +3344,11 @@ async function bulkAdd(
|
|||
})();
|
||||
}
|
||||
|
||||
async function getById<T>(
|
||||
function getById<Key extends string | number, Result = unknown>(
|
||||
table: string,
|
||||
id: string | number
|
||||
): Promise<T | undefined> {
|
||||
const db = getInstance();
|
||||
id: Key,
|
||||
db = getInstance()
|
||||
): Result | undefined {
|
||||
const row = db
|
||||
.prepare<Query>(
|
||||
`
|
||||
|
@ -3003,12 +3368,11 @@ async function getById<T>(
|
|||
return jsonToObject(row.json);
|
||||
}
|
||||
|
||||
async function removeById(
|
||||
function removeById<Key extends string | number>(
|
||||
table: string,
|
||||
id: string | number | Array<string | number>
|
||||
): Promise<void> {
|
||||
const db = getInstance();
|
||||
|
||||
id: Key | Array<Key>,
|
||||
db = getInstance()
|
||||
): void {
|
||||
if (!Array.isArray(id)) {
|
||||
db.prepare<Query>(
|
||||
`
|
||||
|
@ -4922,7 +5286,7 @@ async function resetAttachmentDownloadPending(): Promise<void> {
|
|||
`
|
||||
).run();
|
||||
}
|
||||
function removeAttachmentDownloadJob(id: string): Promise<void> {
|
||||
async function removeAttachmentDownloadJob(id: string): Promise<void> {
|
||||
return removeById(ATTACHMENT_DOWNLOADS_TABLE, id);
|
||||
}
|
||||
function removeAllAttachmentDownloadJobs(): Promise<void> {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { v4 as getGuid } from 'uuid';
|
||||
|
||||
import MessageReceiver from '../textsecure/MessageReceiver';
|
||||
import { IncomingWebSocketRequest } from '../textsecure/WebsocketResources';
|
||||
|
@ -22,11 +23,20 @@ describe('MessageReceiver', () => {
|
|||
const uuid = 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee';
|
||||
const deviceId = 1;
|
||||
|
||||
let oldUuid: string | undefined;
|
||||
let oldDeviceId: number | undefined;
|
||||
|
||||
beforeEach(async () => {
|
||||
oldUuid = window.storage.user.getUuid()?.toString();
|
||||
oldDeviceId = window.storage.user.getDeviceId();
|
||||
await window.storage.user.setUuidAndDeviceId(getGuid(), 2);
|
||||
await window.storage.protocol.hydrateCaches();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (oldUuid !== undefined && oldDeviceId !== undefined) {
|
||||
await window.storage.user.setUuidAndDeviceId(oldUuid, oldDeviceId);
|
||||
}
|
||||
await window.storage.protocol.removeAllUnprocessed();
|
||||
});
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,10 +2,12 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { v4 as getGuid } from 'uuid';
|
||||
|
||||
import { getRandomBytes } from '../../Crypto';
|
||||
import AccountManager from '../../textsecure/AccountManager';
|
||||
import { OuterSignedPrekeyType } from '../../textsecure/Types.d';
|
||||
import { UUID } from '../../types/UUID';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
|
@ -21,15 +23,18 @@ describe('AccountManager', () => {
|
|||
let originalGetIdentityKeyPair: any;
|
||||
let originalLoadSignedPreKeys: any;
|
||||
let originalRemoveSignedPreKey: any;
|
||||
let originalGetUuid: any;
|
||||
let signedPreKeys: Array<OuterSignedPrekeyType>;
|
||||
const DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
const pubKey = getRandomBytes(33);
|
||||
const privKey = getRandomBytes(32);
|
||||
const identityKey = window.Signal.Curve.generateKeyPair();
|
||||
|
||||
beforeEach(async () => {
|
||||
const identityKey = window.Signal.Curve.generateKeyPair();
|
||||
const ourUuid = new UUID(getGuid());
|
||||
|
||||
originalGetUuid = window.textsecure.storage.user.getUuid;
|
||||
originalGetIdentityKeyPair =
|
||||
window.textsecure.storage.protocol.getIdentityKeyPair;
|
||||
originalLoadSignedPreKeys =
|
||||
|
@ -37,12 +42,15 @@ describe('AccountManager', () => {
|
|||
originalRemoveSignedPreKey =
|
||||
window.textsecure.storage.protocol.removeSignedPreKey;
|
||||
|
||||
window.textsecure.storage.user.getUuid = () => ourUuid;
|
||||
|
||||
window.textsecure.storage.protocol.getIdentityKeyPair = async () =>
|
||||
identityKey;
|
||||
window.textsecure.storage.protocol.loadSignedPreKeys = async () =>
|
||||
signedPreKeys;
|
||||
});
|
||||
afterEach(() => {
|
||||
window.textsecure.storage.user.getUuid = originalGetUuid;
|
||||
window.textsecure.storage.protocol.getIdentityKeyPair = originalGetIdentityKeyPair;
|
||||
window.textsecure.storage.protocol.loadSignedPreKeys = originalLoadSignedPreKeys;
|
||||
window.textsecure.storage.protocol.removeSignedPreKey = originalRemoveSignedPreKey;
|
||||
|
@ -51,7 +59,10 @@ describe('AccountManager', () => {
|
|||
describe('encrypted device name', () => {
|
||||
it('roundtrips', async () => {
|
||||
const deviceName = 'v2.5.0 on Ubunto 20.04';
|
||||
const encrypted = await accountManager.encryptDeviceName(deviceName);
|
||||
const encrypted = await accountManager.encryptDeviceName(
|
||||
deviceName,
|
||||
identityKey
|
||||
);
|
||||
if (!encrypted) {
|
||||
throw new Error('failed to encrypt!');
|
||||
}
|
||||
|
@ -62,7 +73,10 @@ describe('AccountManager', () => {
|
|||
});
|
||||
|
||||
it('handles falsey deviceName', async () => {
|
||||
const encrypted = await accountManager.encryptDeviceName('');
|
||||
const encrypted = await accountManager.encryptDeviceName(
|
||||
'',
|
||||
identityKey
|
||||
);
|
||||
assert.strictEqual(encrypted, null);
|
||||
});
|
||||
});
|
||||
|
@ -146,7 +160,10 @@ describe('AccountManager', () => {
|
|||
];
|
||||
|
||||
let count = 0;
|
||||
window.textsecure.storage.protocol.removeSignedPreKey = async keyId => {
|
||||
window.textsecure.storage.protocol.removeSignedPreKey = async (
|
||||
_,
|
||||
keyId
|
||||
) => {
|
||||
if (keyId !== 4) {
|
||||
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
||||
}
|
||||
|
|
|
@ -1,43 +1,56 @@
|
|||
// Copyright 2017-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* global ConversationController, SignalProtocolStore, Whisper */
|
||||
import { assert } from 'chai';
|
||||
import { v4 as getGuid } from 'uuid';
|
||||
|
||||
import { getRandomBytes } from '../../Crypto';
|
||||
import { Address } from '../../types/Address';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import { SignalProtocolStore } from '../../SignalProtocolStore';
|
||||
import type { ConversationModel } from '../../models/conversations';
|
||||
import * as KeyChangeListener from '../../textsecure/KeyChangeListener';
|
||||
|
||||
const { Whisper } = window;
|
||||
|
||||
describe('KeyChangeListener', () => {
|
||||
const STORAGE_KEYS_TO_RESTORE = ['number_id', 'uuid_id'];
|
||||
const oldStorageValues = new Map();
|
||||
let oldNumberId: string | undefined;
|
||||
let oldUuidId: string | undefined;
|
||||
|
||||
const phoneNumberWithKeyChange = '+13016886524'; // nsa
|
||||
const addressString = `${phoneNumberWithKeyChange}.1`;
|
||||
const oldKey = window.Signal.Crypto.getRandomBytes(33);
|
||||
const newKey = window.Signal.Crypto.getRandomBytes(33);
|
||||
let store;
|
||||
const ourUuid = getGuid();
|
||||
const uuidWithKeyChange = getGuid();
|
||||
const address = Address.create(uuidWithKeyChange, 1);
|
||||
const oldKey = getRandomBytes(33);
|
||||
const newKey = getRandomBytes(33);
|
||||
let store: SignalProtocolStore;
|
||||
|
||||
before(async () => {
|
||||
window.ConversationController.reset();
|
||||
await window.ConversationController.load();
|
||||
|
||||
STORAGE_KEYS_TO_RESTORE.forEach(key => {
|
||||
oldStorageValues.set(key, window.textsecure.storage.get(key));
|
||||
});
|
||||
window.textsecure.storage.put('number_id', '+14155555556.2');
|
||||
window.textsecure.storage.put('uuid_id', `${window.getGuid()}.2`);
|
||||
const { storage } = window.textsecure;
|
||||
|
||||
oldNumberId = storage.get('number_id');
|
||||
oldUuidId = storage.get('uuid_id');
|
||||
await storage.put('number_id', '+14155555556.2');
|
||||
await storage.put('uuid_id', `${ourUuid}.2`);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await window.Signal.Data.removeAll();
|
||||
await window.storage.fetch();
|
||||
|
||||
oldStorageValues.forEach((oldValue, key) => {
|
||||
if (oldValue) {
|
||||
window.textsecure.storage.put(key, oldValue);
|
||||
} else {
|
||||
window.textsecure.storage.remove(key);
|
||||
}
|
||||
});
|
||||
const { storage } = window.textsecure;
|
||||
await storage.fetch();
|
||||
|
||||
if (oldNumberId) {
|
||||
await storage.put('number_id', oldNumberId);
|
||||
}
|
||||
if (oldUuidId) {
|
||||
await storage.put('uuid_id', oldUuidId);
|
||||
}
|
||||
});
|
||||
|
||||
let convo;
|
||||
let convo: ConversationModel;
|
||||
|
||||
beforeEach(async () => {
|
||||
window.ConversationController.reset();
|
||||
|
@ -45,46 +58,46 @@ describe('KeyChangeListener', () => {
|
|||
await window.ConversationController.loadPromise();
|
||||
|
||||
convo = window.ConversationController.dangerouslyCreateAndAdd({
|
||||
id: phoneNumberWithKeyChange,
|
||||
id: uuidWithKeyChange,
|
||||
type: 'private',
|
||||
});
|
||||
await window.Signal.Data.saveConversation(convo.attributes);
|
||||
|
||||
store = new SignalProtocolStore();
|
||||
await store.hydrateCaches();
|
||||
Whisper.KeyChangeListener.init(store);
|
||||
return store.saveIdentity(addressString, oldKey);
|
||||
KeyChangeListener.init(store);
|
||||
return store.saveIdentity(address, oldKey);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await window.Signal.Data.removeAllMessagesInConversation(convo.id, {
|
||||
logId: phoneNumberWithKeyChange,
|
||||
logId: uuidWithKeyChange,
|
||||
MessageCollection: Whisper.MessageCollection,
|
||||
});
|
||||
await window.Signal.Data.removeConversation(convo.id, {
|
||||
Conversation: Whisper.Conversation,
|
||||
});
|
||||
|
||||
await store.removeIdentityKey(phoneNumberWithKeyChange);
|
||||
await store.removeIdentityKey(new UUID(uuidWithKeyChange));
|
||||
});
|
||||
|
||||
describe('When we have a conversation with this contact', () => {
|
||||
it('generates a key change notice in the private conversation with this contact', done => {
|
||||
const original = convo.addKeyChange;
|
||||
convo.addKeyChange = keyChangedId => {
|
||||
assert.equal(phoneNumberWithKeyChange, keyChangedId);
|
||||
convo.addKeyChange = async keyChangedId => {
|
||||
assert.equal(uuidWithKeyChange, keyChangedId.toString());
|
||||
convo.addKeyChange = original;
|
||||
done();
|
||||
};
|
||||
store.saveIdentity(addressString, newKey);
|
||||
store.saveIdentity(address, newKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When we have a group with this contact', () => {
|
||||
let groupConvo;
|
||||
let groupConvo: ConversationModel;
|
||||
|
||||
beforeEach(async () => {
|
||||
groupConvo = ConversationController.dangerouslyCreateAndAdd({
|
||||
groupConvo = window.ConversationController.dangerouslyCreateAndAdd({
|
||||
id: 'groupId',
|
||||
type: 'group',
|
||||
members: [convo.id],
|
||||
|
@ -94,7 +107,7 @@ describe('KeyChangeListener', () => {
|
|||
|
||||
afterEach(async () => {
|
||||
await window.Signal.Data.removeAllMessagesInConversation(groupConvo.id, {
|
||||
logId: phoneNumberWithKeyChange,
|
||||
logId: uuidWithKeyChange,
|
||||
MessageCollection: Whisper.MessageCollection,
|
||||
});
|
||||
await window.Signal.Data.removeConversation(groupConvo.id, {
|
||||
|
@ -104,13 +117,13 @@ describe('KeyChangeListener', () => {
|
|||
|
||||
it('generates a key change notice in the group conversation with this contact', done => {
|
||||
const original = groupConvo.addKeyChange;
|
||||
groupConvo.addKeyChange = keyChangedId => {
|
||||
assert.equal(phoneNumberWithKeyChange, keyChangedId);
|
||||
groupConvo.addKeyChange = async keyChangedId => {
|
||||
assert.equal(uuidWithKeyChange, keyChangedId.toString());
|
||||
groupConvo.addKeyChange = original;
|
||||
done();
|
||||
};
|
||||
|
||||
store.saveIdentity(addressString, newKey);
|
||||
store.saveIdentity(address, newKey);
|
||||
});
|
||||
});
|
||||
});
|
198
ts/test-electron/textsecure/generate_keys_test.ts
Normal file
198
ts/test-electron/textsecure/generate_keys_test.ts
Normal file
|
@ -0,0 +1,198 @@
|
|||
// Copyright 2015-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import {
|
||||
typedArrayToArrayBuffer as toArrayBuffer,
|
||||
arrayBufferToBase64 as toBase64,
|
||||
constantTimeEqual,
|
||||
} from '../../Crypto';
|
||||
import { generateKeyPair } from '../../Curve';
|
||||
import AccountManager, {
|
||||
GeneratedKeysType,
|
||||
} from '../../textsecure/AccountManager';
|
||||
import { PreKeyType, SignedPreKeyType } from '../../textsecure/Types.d';
|
||||
import { UUID } from '../../types/UUID';
|
||||
|
||||
const { textsecure } = window;
|
||||
|
||||
const assertEqualArrayBuffers = (a: ArrayBuffer, b: ArrayBuffer) => {
|
||||
assert.isTrue(constantTimeEqual(a, b));
|
||||
};
|
||||
|
||||
describe('Key generation', function thisNeeded() {
|
||||
const count = 10;
|
||||
const ourUuid = new UUID('aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee');
|
||||
this.timeout(count * 2000);
|
||||
|
||||
function itStoresPreKey(keyId: number): void {
|
||||
it(`prekey ${keyId} is valid`, async () => {
|
||||
const keyPair = await textsecure.storage.protocol.loadPreKey(
|
||||
ourUuid,
|
||||
keyId
|
||||
);
|
||||
assert(keyPair, `PreKey ${keyId} not found`);
|
||||
});
|
||||
}
|
||||
function itStoresSignedPreKey(keyId: number): void {
|
||||
it(`signed prekey ${keyId} is valid`, async () => {
|
||||
const keyPair = await textsecure.storage.protocol.loadSignedPreKey(
|
||||
ourUuid,
|
||||
keyId
|
||||
);
|
||||
assert(keyPair, `SignedPreKey ${keyId} not found`);
|
||||
});
|
||||
}
|
||||
async function validateResultKey(
|
||||
resultKey: Pick<PreKeyType, 'keyId' | 'publicKey'>
|
||||
): Promise<void> {
|
||||
const keyPair = await textsecure.storage.protocol.loadPreKey(
|
||||
ourUuid,
|
||||
resultKey.keyId
|
||||
);
|
||||
if (!keyPair) {
|
||||
throw new Error(`PreKey ${resultKey.keyId} not found`);
|
||||
}
|
||||
assertEqualArrayBuffers(
|
||||
resultKey.publicKey,
|
||||
toArrayBuffer(keyPair.publicKey().serialize())
|
||||
);
|
||||
}
|
||||
async function validateResultSignedKey(
|
||||
resultSignedKey: Pick<SignedPreKeyType, 'keyId' | 'publicKey'>
|
||||
) {
|
||||
const keyPair = await textsecure.storage.protocol.loadSignedPreKey(
|
||||
ourUuid,
|
||||
resultSignedKey.keyId
|
||||
);
|
||||
if (!keyPair) {
|
||||
throw new Error(`SignedPreKey ${resultSignedKey.keyId} not found`);
|
||||
}
|
||||
assertEqualArrayBuffers(
|
||||
resultSignedKey.publicKey,
|
||||
toArrayBuffer(keyPair.publicKey().serialize())
|
||||
);
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
const keyPair = generateKeyPair();
|
||||
await textsecure.storage.put('identityKeyMap', {
|
||||
[ourUuid.toString()]: {
|
||||
privKey: toBase64(keyPair.privKey),
|
||||
pubKey: toBase64(keyPair.pubKey),
|
||||
},
|
||||
});
|
||||
await textsecure.storage.user.setUuidAndDeviceId(ourUuid.toString(), 1);
|
||||
await textsecure.storage.protocol.hydrateCaches();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await textsecure.storage.protocol.clearPreKeyStore();
|
||||
await textsecure.storage.protocol.clearSignedPreKeysStore();
|
||||
});
|
||||
|
||||
describe('the first time', () => {
|
||||
let result: GeneratedKeysType;
|
||||
|
||||
before(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const accountManager = new AccountManager({} as any);
|
||||
result = await accountManager.generateKeys(count);
|
||||
});
|
||||
|
||||
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', async () => {
|
||||
await 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: GeneratedKeysType;
|
||||
before(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const accountManager = new AccountManager({} as any);
|
||||
result = await accountManager.generateKeys(count);
|
||||
});
|
||||
|
||||
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', async () => {
|
||||
await 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: GeneratedKeysType;
|
||||
before(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const accountManager = new AccountManager({} as any);
|
||||
result = await accountManager.generateKeys(count);
|
||||
});
|
||||
|
||||
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', async () => {
|
||||
await 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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,6 +7,7 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import PQueue from 'p-queue';
|
||||
import { omit } from 'lodash';
|
||||
|
||||
import EventTarget from './EventTarget';
|
||||
import { WebAPIType } from './WebAPI';
|
||||
|
@ -21,6 +22,7 @@ import {
|
|||
generateRegistrationId,
|
||||
getRandomBytes,
|
||||
typedArrayToArrayBuffer,
|
||||
arrayBufferToBase64,
|
||||
} from '../Crypto';
|
||||
import {
|
||||
generateKeyPair,
|
||||
|
@ -29,7 +31,7 @@ import {
|
|||
} from '../Curve';
|
||||
import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
|
||||
import { ourProfileKeyService } from '../services/ourProfileKey';
|
||||
import { assert } from '../util/assert';
|
||||
import { assert, strictAssert } from '../util/assert';
|
||||
import { getProvisioningUrl } from '../util/getProvisioningUrl';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
|
||||
|
@ -56,7 +58,7 @@ function getIdentifier(id: string | undefined) {
|
|||
return parts[0];
|
||||
}
|
||||
|
||||
type GeneratedKeysType = {
|
||||
export type GeneratedKeysType = {
|
||||
preKeys: Array<{
|
||||
keyId: number;
|
||||
publicKey: ArrayBuffer;
|
||||
|
@ -89,16 +91,10 @@ export default class AccountManager extends EventTarget {
|
|||
return this.server.requestVerificationSMS(number);
|
||||
}
|
||||
|
||||
async encryptDeviceName(name: string, providedIdentityKey?: KeyPairType) {
|
||||
async encryptDeviceName(name: string, identityKey: KeyPairType) {
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
const identityKey =
|
||||
providedIdentityKey ||
|
||||
(await window.textsecure.storage.protocol.getIdentityKeyPair());
|
||||
if (!identityKey) {
|
||||
throw new Error('Identity key was not provided and is not in database!');
|
||||
}
|
||||
const encrypted = await window.Signal.Crypto.encryptDeviceName(
|
||||
name,
|
||||
identityKey.pubKey
|
||||
|
@ -114,7 +110,10 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
async decryptDeviceName(base64: string) {
|
||||
const identityKey = await window.textsecure.storage.protocol.getIdentityKeyPair();
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const identityKey = await window.textsecure.storage.protocol.getIdentityKeyPair(
|
||||
ourUuid
|
||||
);
|
||||
if (!identityKey) {
|
||||
throw new Error('decryptDeviceName: No identity key pair!');
|
||||
}
|
||||
|
@ -144,8 +143,19 @@ export default class AccountManager extends EventTarget {
|
|||
if (isNameEncrypted) {
|
||||
return;
|
||||
}
|
||||
const deviceName = window.textsecure.storage.user.getDeviceName();
|
||||
const base64 = await this.encryptDeviceName(deviceName || '');
|
||||
const { storage } = window.textsecure;
|
||||
const deviceName = storage.user.getDeviceName();
|
||||
const identityKeyPair = await storage.protocol.getIdentityKeyPair(
|
||||
storage.user.getCheckedUuid()
|
||||
);
|
||||
strictAssert(
|
||||
identityKeyPair !== undefined,
|
||||
"Can't encrypt device name without identity key pair"
|
||||
);
|
||||
const base64 = await this.encryptDeviceName(
|
||||
deviceName || '',
|
||||
identityKeyPair
|
||||
);
|
||||
|
||||
if (base64) {
|
||||
await this.server.updateDeviceName(base64);
|
||||
|
@ -310,6 +320,7 @@ export default class AccountManager extends EventTarget {
|
|||
|
||||
async rotateSignedPreKey() {
|
||||
return this.queueTask(async () => {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
|
||||
if (typeof signedKeyId !== 'number') {
|
||||
throw new Error('Invalid signedKeyId');
|
||||
|
@ -318,7 +329,7 @@ export default class AccountManager extends EventTarget {
|
|||
const store = window.textsecure.storage.protocol;
|
||||
const { server, cleanSignedPreKeys } = this;
|
||||
|
||||
const existingKeys = await store.loadSignedPreKeys();
|
||||
const existingKeys = await store.loadSignedPreKeys(ourUuid);
|
||||
existingKeys.sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
|
||||
const confirmedKeys = existingKeys.filter(key => key.confirmed);
|
||||
const mostRecent = confirmedKeys[0];
|
||||
|
@ -332,7 +343,7 @@ export default class AccountManager extends EventTarget {
|
|||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return store
|
||||
.getIdentityKeyPair()
|
||||
.getIdentityKeyPair(ourUuid)
|
||||
.then(
|
||||
async (identityKey: KeyPairType | undefined) => {
|
||||
if (!identityKey) {
|
||||
|
@ -357,7 +368,7 @@ export default class AccountManager extends EventTarget {
|
|||
window.log.info('Saving new signed prekey', res.keyId);
|
||||
return Promise.all([
|
||||
window.textsecure.storage.put('signedKeyId', signedKeyId + 1),
|
||||
store.storeSignedPreKey(res.keyId, res.keyPair),
|
||||
store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair),
|
||||
server.setSignedPreKey({
|
||||
keyId: res.keyId,
|
||||
publicKey: res.keyPair.pubKey,
|
||||
|
@ -369,7 +380,12 @@ export default class AccountManager extends EventTarget {
|
|||
window.log.info('Confirming new signed prekey', res.keyId);
|
||||
return Promise.all([
|
||||
window.textsecure.storage.remove('signedKeyRotationRejected'),
|
||||
store.storeSignedPreKey(res.keyId, res.keyPair, confirmed),
|
||||
store.storeSignedPreKey(
|
||||
ourUuid,
|
||||
res.keyId,
|
||||
res.keyPair,
|
||||
confirmed
|
||||
),
|
||||
]);
|
||||
})
|
||||
.then(cleanSignedPreKeys);
|
||||
|
@ -409,9 +425,10 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
async cleanSignedPreKeys() {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const store = window.textsecure.storage.protocol;
|
||||
|
||||
const allKeys = await store.loadSignedPreKeys();
|
||||
const allKeys = await store.loadSignedPreKeys(ourUuid);
|
||||
allKeys.sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
|
||||
const confirmed = allKeys.filter(key => key.confirmed);
|
||||
const unconfirmed = allKeys.filter(key => !key.confirmed);
|
||||
|
@ -448,7 +465,7 @@ export default class AccountManager extends EventTarget {
|
|||
window.log.info(
|
||||
`Removing signed prekey: ${key.keyId} with timestamp ${timestamp}${confirmedText}`
|
||||
);
|
||||
await store.removeSignedPreKey(key.keyId);
|
||||
await store.removeSignedPreKey(ourUuid, key.keyId);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
@ -464,17 +481,14 @@ export default class AccountManager extends EventTarget {
|
|||
readReceipts?: boolean | null,
|
||||
options: { accessKey?: ArrayBuffer; uuid?: string } = {}
|
||||
): Promise<void> {
|
||||
const { storage } = window.textsecure;
|
||||
const { accessKey, uuid } = options;
|
||||
let password = btoa(utils.getString(getRandomBytes(16)));
|
||||
password = password.substring(0, password.length - 2);
|
||||
const registrationId = generateRegistrationId();
|
||||
|
||||
const previousNumber = getIdentifier(
|
||||
window.textsecure.storage.get('number_id')
|
||||
);
|
||||
const previousUuid = getIdentifier(
|
||||
window.textsecure.storage.get('uuid_id')
|
||||
);
|
||||
const previousNumber = getIdentifier(storage.get('number_id'));
|
||||
const previousUuid = getIdentifier(storage.get('uuid_id'));
|
||||
|
||||
let encryptedDeviceName;
|
||||
if (deviceName) {
|
||||
|
@ -500,7 +514,10 @@ export default class AccountManager extends EventTarget {
|
|||
{ accessKey, uuid }
|
||||
);
|
||||
|
||||
const uuidChanged = previousUuid && uuid && previousUuid !== uuid;
|
||||
const ourUuid = uuid || response.uuid;
|
||||
strictAssert(ourUuid !== undefined, 'Should have UUID after registration');
|
||||
|
||||
const uuidChanged = previousUuid && ourUuid && previousUuid !== ourUuid;
|
||||
|
||||
// We only consider the number changed if we didn't have a UUID before
|
||||
const numberChanged =
|
||||
|
@ -519,7 +536,7 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
try {
|
||||
await window.textsecure.storage.protocol.removeAllData();
|
||||
await storage.protocol.removeAllData();
|
||||
window.log.info('Successfully deleted previous data');
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
|
@ -530,23 +547,34 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
await Promise.all([
|
||||
window.textsecure.storage.remove('identityKey'),
|
||||
window.textsecure.storage.user.removeCredentials(),
|
||||
window.textsecure.storage.remove('registrationId'),
|
||||
window.textsecure.storage.remove('regionCode'),
|
||||
window.textsecure.storage.remove('userAgent'),
|
||||
window.textsecure.storage.remove('profileKey'),
|
||||
window.textsecure.storage.remove('read-receipt-setting'),
|
||||
storage.user.removeCredentials(),
|
||||
storage.remove('regionCode'),
|
||||
storage.remove('userAgent'),
|
||||
storage.remove('profileKey'),
|
||||
storage.remove('read-receipt-setting'),
|
||||
]);
|
||||
|
||||
if (previousUuid) {
|
||||
await Promise.all([
|
||||
storage.put(
|
||||
'identityKeyMap',
|
||||
omit(storage.get('identityKeyMap') || {}, previousUuid)
|
||||
),
|
||||
storage.put(
|
||||
'registrationIdMap',
|
||||
omit(storage.get('registrationIdMap') || {}, previousUuid)
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
// `setCredentials` needs to be called
|
||||
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
|
||||
// indirectly calls `ConversationController.getConverationId()` which
|
||||
// initializes the conversation for the given number (our number) which
|
||||
// calls out to the user storage API to get the stored UUID and number
|
||||
// information.
|
||||
await window.textsecure.storage.user.setCredentials({
|
||||
uuid,
|
||||
await storage.user.setCredentials({
|
||||
uuid: ourUuid,
|
||||
number,
|
||||
deviceId: response.deviceId ?? 1,
|
||||
deviceName: deviceName ?? undefined,
|
||||
|
@ -558,7 +586,7 @@ export default class AccountManager extends EventTarget {
|
|||
// below.
|
||||
const conversationId = window.ConversationController.ensureContactIds({
|
||||
e164: number,
|
||||
uuid,
|
||||
uuid: ourUuid,
|
||||
highTrust: true,
|
||||
});
|
||||
|
||||
|
@ -568,36 +596,42 @@ export default class AccountManager extends EventTarget {
|
|||
|
||||
// update our own identity key, which may have changed
|
||||
// if we're relinking after a reinstall on the master device
|
||||
await window.textsecure.storage.protocol.saveIdentityWithAttributes(
|
||||
uuid || number,
|
||||
{
|
||||
publicKey: identityKeyPair.pubKey,
|
||||
firstUse: true,
|
||||
timestamp: Date.now(),
|
||||
verified: window.textsecure.storage.protocol.VerifiedStatus.VERIFIED,
|
||||
nonblockingApproval: true,
|
||||
}
|
||||
);
|
||||
await storage.protocol.saveIdentityWithAttributes(ourUuid, {
|
||||
publicKey: identityKeyPair.pubKey,
|
||||
firstUse: true,
|
||||
timestamp: Date.now(),
|
||||
verified: storage.protocol.VerifiedStatus.VERIFIED,
|
||||
nonblockingApproval: true,
|
||||
});
|
||||
|
||||
await window.textsecure.storage.put('identityKey', identityKeyPair);
|
||||
await window.textsecure.storage.put('registrationId', registrationId);
|
||||
const identityKeyMap = {
|
||||
...(storage.get('identityKeyMap') || {}),
|
||||
[ourUuid]: {
|
||||
pubKey: arrayBufferToBase64(identityKeyPair.pubKey),
|
||||
privKey: arrayBufferToBase64(identityKeyPair.privKey),
|
||||
},
|
||||
};
|
||||
const registrationIdMap = {
|
||||
...(storage.get('registrationIdMap') || {}),
|
||||
[ourUuid]: registrationId,
|
||||
};
|
||||
|
||||
await storage.put('identityKeyMap', identityKeyMap);
|
||||
await storage.put('registrationIdMap', registrationIdMap);
|
||||
if (profileKey) {
|
||||
await ourProfileKeyService.set(profileKey);
|
||||
}
|
||||
if (userAgent) {
|
||||
await window.textsecure.storage.put('userAgent', userAgent);
|
||||
await storage.put('userAgent', userAgent);
|
||||
}
|
||||
|
||||
await window.textsecure.storage.put(
|
||||
'read-receipt-setting',
|
||||
Boolean(readReceipts)
|
||||
);
|
||||
await storage.put('read-receipt-setting', Boolean(readReceipts));
|
||||
|
||||
const regionCode = window.libphonenumber.util.getRegionCodeForNumber(
|
||||
number
|
||||
);
|
||||
await window.textsecure.storage.put('regionCode', regionCode);
|
||||
await window.textsecure.storage.protocol.hydrateCaches();
|
||||
await storage.put('regionCode', regionCode);
|
||||
await storage.protocol.hydrateCaches();
|
||||
}
|
||||
|
||||
async clearSessionsAndPreKeys() {
|
||||
|
@ -626,7 +660,8 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
window.log.info('confirmKeys: confirming key', key.keyId);
|
||||
await store.storeSignedPreKey(key.keyId, key.keyPair, confirmed);
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
await store.storeSignedPreKey(ourUuid, key.keyId, key.keyPair, confirmed);
|
||||
}
|
||||
|
||||
async generateKeys(count: number, providedProgressCallback?: Function) {
|
||||
|
@ -636,6 +671,7 @@ export default class AccountManager extends EventTarget {
|
|||
: null;
|
||||
const startId = window.textsecure.storage.get('maxPreKeyId', 1);
|
||||
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
if (typeof startId !== 'number') {
|
||||
throw new Error('Invalid maxPreKeyId');
|
||||
|
@ -645,7 +681,7 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
|
||||
const store = window.textsecure.storage.protocol;
|
||||
return store.getIdentityKeyPair().then(async identityKey => {
|
||||
return store.getIdentityKeyPair(ourUuid).then(async identityKey => {
|
||||
if (!identityKey) {
|
||||
throw new Error('generateKeys: No identity key pair!');
|
||||
}
|
||||
|
@ -659,7 +695,7 @@ export default class AccountManager extends EventTarget {
|
|||
for (let keyId = startId; keyId < startId + count; keyId += 1) {
|
||||
promises.push(
|
||||
Promise.resolve(generatePreKey(keyId)).then(async res => {
|
||||
await store.storePreKey(res.keyId, res.keyPair);
|
||||
await store.storePreKey(ourUuid, res.keyId, res.keyPair);
|
||||
result.preKeys.push({
|
||||
keyId: res.keyId,
|
||||
publicKey: res.keyPair.pubKey,
|
||||
|
@ -674,7 +710,7 @@ export default class AccountManager extends EventTarget {
|
|||
promises.push(
|
||||
Promise.resolve(generateSignedPreKey(identityKey, signedKeyId)).then(
|
||||
async res => {
|
||||
await store.storeSignedPreKey(res.keyId, res.keyPair);
|
||||
await store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair);
|
||||
result.signedPreKey = {
|
||||
keyId: res.keyId,
|
||||
publicKey: res.keyPair.pubKey,
|
||||
|
|
25
ts/textsecure/KeyChangeListener.ts
Normal file
25
ts/textsecure/KeyChangeListener.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2017-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { UUID } from '../types/UUID';
|
||||
import type { SignalProtocolStore } from '../SignalProtocolStore';
|
||||
|
||||
export function init(signalProtocolStore: SignalProtocolStore): void {
|
||||
signalProtocolStore.on(
|
||||
'keychange',
|
||||
async (uuid: UUID): Promise<void> => {
|
||||
const conversation = await window.ConversationController.getOrCreateAndWait(
|
||||
uuid.toString(),
|
||||
'private'
|
||||
);
|
||||
conversation.addKeyChange(uuid);
|
||||
|
||||
const groups = await window.ConversationController.getAllGroupsInvolvingId(
|
||||
conversation.id
|
||||
);
|
||||
for (const group of groups) {
|
||||
group.addKeyChange(uuid);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -45,6 +45,9 @@ import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
|||
import { Zone } from '../util/Zone';
|
||||
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
|
||||
import { DownloadedAttachmentType } from '../types/Attachment';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID } from '../types/UUID';
|
||||
import * as Errors from '../types/errors';
|
||||
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
|
@ -754,8 +757,9 @@ export default class MessageReceiver
|
|||
pendingSessions: true,
|
||||
pendingUnprocessed: true,
|
||||
});
|
||||
const sessionStore = new Sessions({ zone });
|
||||
const identityKeyStore = new IdentityKeys({ zone });
|
||||
const ourUuid = this.storage.user.getCheckedUuid();
|
||||
const sessionStore = new Sessions({ zone, ourUuid });
|
||||
const identityKeyStore = new IdentityKeys({ zone, ourUuid });
|
||||
const failed: Array<UnprocessedType> = [];
|
||||
|
||||
// Below we:
|
||||
|
@ -1228,18 +1232,12 @@ export default class MessageReceiver
|
|||
ciphertext: Uint8Array
|
||||
): Promise<DecryptSealedSenderResult> {
|
||||
const localE164 = this.storage.user.getNumber();
|
||||
const localUuid = this.storage.user.getUuid();
|
||||
const ourUuid = this.storage.user.getCheckedUuid();
|
||||
const localDeviceId = parseIntOrThrow(
|
||||
this.storage.user.getDeviceId(),
|
||||
'MessageReceiver.decryptSealedSender: localDeviceId'
|
||||
);
|
||||
|
||||
if (!localUuid) {
|
||||
throw new Error(
|
||||
'MessageReceiver.decryptSealedSender: Failed to fetch local UUID'
|
||||
);
|
||||
}
|
||||
|
||||
const logId = this.getEnvelopeId(envelope);
|
||||
|
||||
const { unsealedContent: messageContent, certificate } = envelope;
|
||||
|
@ -1280,9 +1278,12 @@ export default class MessageReceiver
|
|||
);
|
||||
const sealedSenderIdentifier = certificate.senderUuid();
|
||||
const sealedSenderSourceDevice = certificate.senderDeviceId();
|
||||
const senderKeyStore = new SenderKeys();
|
||||
const senderKeyStore = new SenderKeys({ ourUuid });
|
||||
|
||||
const address = `${sealedSenderIdentifier}.${sealedSenderSourceDevice}`;
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
Address.create(sealedSenderIdentifier, sealedSenderSourceDevice)
|
||||
);
|
||||
|
||||
const plaintext = await this.storage.protocol.enqueueSenderKeyJob(
|
||||
address,
|
||||
|
@ -1305,11 +1306,22 @@ export default class MessageReceiver
|
|||
'unidentified message/passing to sealedSenderDecryptMessage'
|
||||
);
|
||||
|
||||
const preKeyStore = new PreKeys();
|
||||
const signedPreKeyStore = new SignedPreKeys();
|
||||
const preKeyStore = new PreKeys({ ourUuid });
|
||||
const signedPreKeyStore = new SignedPreKeys({ ourUuid });
|
||||
|
||||
const sealedSenderIdentifier = envelope.sourceUuid || envelope.source;
|
||||
const address = `${sealedSenderIdentifier}.${envelope.sourceDevice}`;
|
||||
const sealedSenderIdentifier = envelope.sourceUuid;
|
||||
strictAssert(
|
||||
sealedSenderIdentifier !== undefined,
|
||||
'Empty sealed sender identifier'
|
||||
);
|
||||
strictAssert(
|
||||
envelope.sourceDevice !== undefined,
|
||||
'Empty sealed sender device'
|
||||
);
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
Address.create(sealedSenderIdentifier, envelope.sourceDevice)
|
||||
);
|
||||
const unsealedPlaintext = await this.storage.protocol.enqueueSessionJob(
|
||||
address,
|
||||
() =>
|
||||
|
@ -1318,7 +1330,7 @@ export default class MessageReceiver
|
|||
PublicKey.deserialize(Buffer.from(this.serverTrustRoot)),
|
||||
envelope.serverTimestamp,
|
||||
localE164 || null,
|
||||
localUuid,
|
||||
ourUuid.toString(),
|
||||
localDeviceId,
|
||||
sessionStore,
|
||||
identityKeyStore,
|
||||
|
@ -1341,11 +1353,20 @@ export default class MessageReceiver
|
|||
const logId = this.getEnvelopeId(envelope);
|
||||
const envelopeTypeEnum = Proto.Envelope.Type;
|
||||
|
||||
const identifier = envelope.sourceUuid || envelope.source;
|
||||
const identifier = envelope.sourceUuid;
|
||||
const { sourceDevice } = envelope;
|
||||
|
||||
const preKeyStore = new PreKeys();
|
||||
const signedPreKeyStore = new SignedPreKeys();
|
||||
const ourUuid = this.storage.user.getCheckedUuid();
|
||||
const preKeyStore = new PreKeys({ ourUuid });
|
||||
const signedPreKeyStore = new SignedPreKeys({ ourUuid });
|
||||
|
||||
strictAssert(identifier !== undefined, 'Empty identifier');
|
||||
strictAssert(sourceDevice !== undefined, 'Empty source device');
|
||||
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
Address.create(identifier, sourceDevice)
|
||||
);
|
||||
|
||||
if (envelope.type === envelopeTypeEnum.PLAINTEXT_CONTENT) {
|
||||
window.log.info(`decrypt/${logId}: plaintext message`);
|
||||
|
@ -1368,7 +1389,6 @@ export default class MessageReceiver
|
|||
}
|
||||
const signalMessage = SignalMessage.deserialize(Buffer.from(ciphertext));
|
||||
|
||||
const address = `${identifier}.${sourceDevice}`;
|
||||
const plaintext = await this.storage.protocol.enqueueSessionJob(
|
||||
address,
|
||||
async () =>
|
||||
|
@ -1400,7 +1420,6 @@ export default class MessageReceiver
|
|||
Buffer.from(ciphertext)
|
||||
);
|
||||
|
||||
const address = `${identifier}.${sourceDevice}`;
|
||||
const plaintext = await this.storage.protocol.enqueueSessionJob(
|
||||
address,
|
||||
async () =>
|
||||
|
@ -1562,7 +1581,7 @@ export default class MessageReceiver
|
|||
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
||||
const { source, sourceUuid } = envelope;
|
||||
const ourE164 = this.storage.user.getNumber();
|
||||
const ourUuid = this.storage.user.getUuid();
|
||||
const ourUuid = this.storage.user.getCheckedUuid().toString();
|
||||
const isMe =
|
||||
(source && ourE164 && source === ourE164) ||
|
||||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||
|
@ -1613,7 +1632,7 @@ export default class MessageReceiver
|
|||
);
|
||||
let p: Promise<void> = Promise.resolve();
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const destination = envelope.sourceUuid || envelope.source;
|
||||
const destination = envelope.sourceUuid;
|
||||
if (!destination) {
|
||||
throw new Error(
|
||||
'MessageReceiver.handleDataMessage: source and sourceUuid were falsey'
|
||||
|
@ -1651,7 +1670,7 @@ export default class MessageReceiver
|
|||
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
||||
const { source, sourceUuid } = envelope;
|
||||
const ourE164 = this.storage.user.getNumber();
|
||||
const ourUuid = this.storage.user.getUuid();
|
||||
const ourUuid = this.storage.user.getCheckedUuid().toString();
|
||||
const isMe =
|
||||
(source && ourE164 && source === ourE164) ||
|
||||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||
|
@ -1711,8 +1730,7 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
const { timestamp } = envelope;
|
||||
const identifier =
|
||||
envelope.groupId || envelope.sourceUuid || envelope.source;
|
||||
const identifier = envelope.groupId || envelope.sourceUuid;
|
||||
const conversation = window.ConversationController.get(identifier);
|
||||
|
||||
try {
|
||||
|
@ -1848,7 +1866,7 @@ export default class MessageReceiver
|
|||
// Note: we don't call removeFromCache here because this message can be combined
|
||||
// with a dataMessage, for example. That processing will dictate cache removal.
|
||||
|
||||
const identifier = envelope.sourceUuid || envelope.source;
|
||||
const identifier = envelope.sourceUuid;
|
||||
const { sourceDevice } = envelope;
|
||||
if (!identifier) {
|
||||
throw new Error(
|
||||
|
@ -1865,8 +1883,12 @@ export default class MessageReceiver
|
|||
const senderKeyDistributionMessage = SenderKeyDistributionMessage.deserialize(
|
||||
Buffer.from(distributionMessage)
|
||||
);
|
||||
const senderKeyStore = new SenderKeys();
|
||||
const address = `${identifier}.${sourceDevice}`;
|
||||
const ourUuid = this.storage.user.getCheckedUuid();
|
||||
const senderKeyStore = new SenderKeys({ ourUuid });
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
Address.create(identifier, sourceDevice)
|
||||
);
|
||||
|
||||
await this.storage.protocol.enqueueSenderKeyJob(
|
||||
address,
|
||||
|
@ -2109,11 +2131,11 @@ export default class MessageReceiver
|
|||
syncMessage: ProcessedSyncMessage
|
||||
): Promise<void> {
|
||||
const ourNumber = this.storage.user.getNumber();
|
||||
const ourUuid = this.storage.user.getUuid();
|
||||
const ourUuid = this.storage.user.getCheckedUuid();
|
||||
|
||||
const fromSelfSource = envelope.source && envelope.source === ourNumber;
|
||||
const fromSelfSourceUuid =
|
||||
envelope.sourceUuid && envelope.sourceUuid === ourUuid;
|
||||
envelope.sourceUuid && envelope.sourceUuid === ourUuid.toString();
|
||||
if (!fromSelfSource && !fromSelfSourceUuid) {
|
||||
throw new Error('Received sync message from another number');
|
||||
}
|
||||
|
@ -2536,8 +2558,14 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
private async handleEndSession(identifier: string): Promise<void> {
|
||||
const theirUuid = UUID.lookup(identifier);
|
||||
if (!theirUuid) {
|
||||
window.log.warn(`handleEndSession: uuid not found for ${identifier}`);
|
||||
return;
|
||||
}
|
||||
|
||||
window.log.info(`handleEndSession: closing sessions for ${identifier}`);
|
||||
await this.storage.protocol.archiveAllSessions(identifier);
|
||||
await this.storage.protocol.archiveAllSessions(theirUuid);
|
||||
}
|
||||
|
||||
private async processDecrypted(
|
||||
|
|
|
@ -32,6 +32,9 @@ import {
|
|||
} from './Errors';
|
||||
import { CallbackResultType, CustomError } from './Types.d';
|
||||
import { isValidNumber } from '../types/PhoneNumber';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID } from '../types/UUID';
|
||||
import { Sessions, IdentityKeys } from '../LibSignalStores';
|
||||
import { typedArrayToArrayBuffer as toArrayBuffer } from '../Crypto';
|
||||
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
||||
|
@ -237,9 +240,11 @@ export default class OutgoingMessage {
|
|||
recurse?: boolean
|
||||
): () => Promise<void> {
|
||||
return async () => {
|
||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds(
|
||||
identifier
|
||||
);
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
|
||||
ourUuid,
|
||||
identifier,
|
||||
});
|
||||
if (deviceIds.length === 0) {
|
||||
this.registerError(
|
||||
identifier,
|
||||
|
@ -386,9 +391,12 @@ export default class OutgoingMessage {
|
|||
|
||||
// We don't send to ourselves unless sealedSender is enabled
|
||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||
if ((identifier === ourNumber || identifier === ourUuid) && !sealedSender) {
|
||||
if (
|
||||
(identifier === ourNumber || identifier === ourUuid.toString()) &&
|
||||
!sealedSender
|
||||
) {
|
||||
deviceIds = reject(
|
||||
deviceIds,
|
||||
deviceId =>
|
||||
|
@ -399,18 +407,22 @@ export default class OutgoingMessage {
|
|||
);
|
||||
}
|
||||
|
||||
const sessionStore = new Sessions();
|
||||
const identityKeyStore = new IdentityKeys();
|
||||
const sessionStore = new Sessions({ ourUuid });
|
||||
const identityKeyStore = new IdentityKeys({ ourUuid });
|
||||
|
||||
return Promise.all(
|
||||
deviceIds.map(async destinationDeviceId => {
|
||||
const address = `${identifier}.${destinationDeviceId}`;
|
||||
const theirUuid = UUID.checkedLookup(identifier);
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
new Address(theirUuid, destinationDeviceId)
|
||||
);
|
||||
|
||||
return window.textsecure.storage.protocol.enqueueSessionJob<SendMetadata>(
|
||||
address,
|
||||
async () => {
|
||||
const protocolAddress = ProtocolAddress.new(
|
||||
identifier,
|
||||
theirUuid.toString(),
|
||||
destinationDeviceId
|
||||
);
|
||||
|
||||
|
@ -566,7 +578,10 @@ export default class OutgoingMessage {
|
|||
p = Promise.all(
|
||||
error.response.staleDevices.map(async (deviceId: number) => {
|
||||
await window.textsecure.storage.protocol.archiveSession(
|
||||
`${identifier}.${deviceId}`
|
||||
new QualifiedAddress(
|
||||
ourUuid,
|
||||
new Address(UUID.checkedLookup(identifier), deviceId)
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
@ -595,7 +610,7 @@ export default class OutgoingMessage {
|
|||
|
||||
window.log.info('closing all sessions for', identifier);
|
||||
window.textsecure.storage.protocol
|
||||
.archiveAllSessions(identifier)
|
||||
.archiveAllSessions(UUID.checkedLookup(identifier))
|
||||
.then(
|
||||
() => {
|
||||
throw error;
|
||||
|
@ -623,10 +638,13 @@ export default class OutgoingMessage {
|
|||
identifier: string,
|
||||
deviceIdsToRemove: Array<number>
|
||||
): Promise<void> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const theirUuid = UUID.checkedLookup(identifier);
|
||||
|
||||
await Promise.all(
|
||||
deviceIdsToRemove.map(async deviceId => {
|
||||
await window.textsecure.storage.protocol.archiveSession(
|
||||
`${identifier}.${deviceId}`
|
||||
new QualifiedAddress(ourUuid, new Address(theirUuid, deviceId))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
@ -675,9 +693,11 @@ export default class OutgoingMessage {
|
|||
);
|
||||
}
|
||||
|
||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds(
|
||||
identifier
|
||||
);
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
|
||||
ourUuid,
|
||||
identifier,
|
||||
});
|
||||
if (deviceIds.length === 0) {
|
||||
await this.getKeysForIdentifier(identifier);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ import {
|
|||
|
||||
import { assert } from '../util/assert';
|
||||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID } from '../types/UUID';
|
||||
import { SenderKeys } from '../LibSignalStores';
|
||||
import {
|
||||
ChallengeType,
|
||||
|
@ -737,14 +740,14 @@ export default class MessageSender {
|
|||
}
|
||||
|
||||
const myE164 = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||
|
||||
const groupMembers = groupV2?.members || groupV1?.members || [];
|
||||
|
||||
// We should always have a UUID but have this check just in case we don't.
|
||||
let isNotMe: (recipient: string) => boolean;
|
||||
if (myUuid) {
|
||||
isNotMe = r => r !== myE164 && r !== myUuid;
|
||||
isNotMe = r => r !== myE164 && r !== myUuid.toString();
|
||||
} else {
|
||||
isNotMe = r => r !== myE164;
|
||||
}
|
||||
|
@ -1030,8 +1033,7 @@ export default class MessageSender {
|
|||
isUpdate?: boolean;
|
||||
options?: SendOptionsType;
|
||||
}>): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const dataMessage = Proto.DataMessage.decode(
|
||||
new FIXMEU8(encodedDataMessage)
|
||||
|
@ -1086,7 +1088,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp,
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1097,8 +1099,7 @@ export default class MessageSender {
|
|||
async sendRequestBlockSyncMessage(
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.BLOCKED;
|
||||
|
@ -1110,7 +1111,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.IMPLICIT,
|
||||
|
@ -1121,8 +1122,7 @@ export default class MessageSender {
|
|||
async sendRequestConfigurationSyncMessage(
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.CONFIGURATION;
|
||||
|
@ -1134,7 +1134,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.IMPLICIT,
|
||||
|
@ -1145,8 +1145,7 @@ export default class MessageSender {
|
|||
async sendRequestGroupSyncMessage(
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.GROUPS;
|
||||
|
@ -1158,7 +1157,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.IMPLICIT,
|
||||
|
@ -1169,8 +1168,7 @@ export default class MessageSender {
|
|||
async sendRequestContactSyncMessage(
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.CONTACTS;
|
||||
|
@ -1182,7 +1180,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.IMPLICIT,
|
||||
|
@ -1193,8 +1191,7 @@ export default class MessageSender {
|
|||
async sendFetchManifestSyncMessage(
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const fetchLatest = new Proto.SyncMessage.FetchLatest();
|
||||
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST;
|
||||
|
@ -1207,7 +1204,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.IMPLICIT,
|
||||
|
@ -1218,8 +1215,7 @@ export default class MessageSender {
|
|||
async sendFetchLocalProfileSyncMessage(
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const fetchLatest = new Proto.SyncMessage.FetchLatest();
|
||||
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.LOCAL_PROFILE;
|
||||
|
@ -1232,7 +1228,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.IMPLICIT,
|
||||
|
@ -1243,8 +1239,7 @@ export default class MessageSender {
|
|||
async sendRequestKeySyncMessage(
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const request = new Proto.SyncMessage.Request();
|
||||
request.type = Proto.SyncMessage.Request.Type.KEYS;
|
||||
|
@ -1257,7 +1252,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.IMPLICIT,
|
||||
|
@ -1273,8 +1268,7 @@ export default class MessageSender {
|
|||
}>,
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.read = [];
|
||||
|
@ -1289,7 +1283,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1305,8 +1299,7 @@ export default class MessageSender {
|
|||
}>,
|
||||
options?: SendOptionsType
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
syncMessage.viewed = views.map(view => new Proto.SyncMessage.Viewed(view));
|
||||
|
@ -1316,7 +1309,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1330,8 +1323,7 @@ export default class MessageSender {
|
|||
timestamp: number,
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
|
||||
|
@ -1349,7 +1341,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1366,8 +1358,7 @@ export default class MessageSender {
|
|||
}>,
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
|
||||
|
@ -1390,7 +1381,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1406,8 +1397,7 @@ export default class MessageSender {
|
|||
}>,
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ENUM = Proto.SyncMessage.StickerPackOperation.Type;
|
||||
|
||||
const packOperations = operations.map(item => {
|
||||
|
@ -1430,7 +1420,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: contentMessage,
|
||||
timestamp: Date.now(),
|
||||
contentHint: ContentHint.IMPLICIT,
|
||||
|
@ -1445,8 +1435,7 @@ export default class MessageSender {
|
|||
identityKey: Readonly<ArrayBuffer>,
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const now = Date.now();
|
||||
|
||||
if (!destinationE164 && !destinationUuid) {
|
||||
|
@ -1488,7 +1477,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
return this.sendIndividualProto({
|
||||
identifier: myUuid || myNumber,
|
||||
identifier: myUuid.toString(),
|
||||
proto: secondMessage,
|
||||
timestamp: now,
|
||||
contentHint: ContentHint.RESENDABLE,
|
||||
|
@ -1675,6 +1664,7 @@ export default class MessageSender {
|
|||
proto.timestamp = timestamp;
|
||||
|
||||
const identifier = uuid || e164;
|
||||
const theirUuid = UUID.checkedLookup(identifier);
|
||||
|
||||
const logError = (prefix: string) => (error: Error) => {
|
||||
window.log.error(prefix, error && error.stack ? error.stack : error);
|
||||
|
@ -1684,7 +1674,7 @@ export default class MessageSender {
|
|||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
||||
const sendToContactPromise = window.textsecure.storage.protocol
|
||||
.archiveAllSessions(identifier)
|
||||
.archiveAllSessions(theirUuid)
|
||||
.catch(logError('resetSession/archiveAllSessions1 error:'))
|
||||
.then(async () => {
|
||||
window.log.info(
|
||||
|
@ -1706,14 +1696,14 @@ export default class MessageSender {
|
|||
})
|
||||
.then(async result => {
|
||||
await window.textsecure.storage.protocol
|
||||
.archiveAllSessions(identifier)
|
||||
.archiveAllSessions(theirUuid)
|
||||
.catch(logError('resetSession/archiveAllSessions2 error:'));
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||
// We already sent the reset session to our other devices in the code above!
|
||||
if ((e164 && e164 === myNumber) || (uuid && uuid === myUuid)) {
|
||||
return sendToContactPromise;
|
||||
|
@ -1882,7 +1872,7 @@ export default class MessageSender {
|
|||
: undefined;
|
||||
|
||||
const myE164 = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||
const identifiers = recipients.filter(id => id !== myE164 && id !== myUuid);
|
||||
|
||||
if (identifiers.length === 0) {
|
||||
|
@ -1921,20 +1911,21 @@ export default class MessageSender {
|
|||
async getSenderKeyDistributionMessage(
|
||||
distributionId: string
|
||||
): Promise<SenderKeyDistributionMessage> {
|
||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
||||
if (!ourUuid) {
|
||||
throw new Error(
|
||||
'getSenderKeyDistributionMessage: Failed to fetch our UUID!'
|
||||
);
|
||||
}
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourDeviceId = parseIntOrThrow(
|
||||
window.textsecure.storage.user.getDeviceId(),
|
||||
'getSenderKeyDistributionMessage'
|
||||
);
|
||||
|
||||
const protocolAddress = ProtocolAddress.new(ourUuid, ourDeviceId);
|
||||
const address = `${ourUuid}.${ourDeviceId}`;
|
||||
const senderKeyStore = new SenderKeys();
|
||||
const protocolAddress = ProtocolAddress.new(
|
||||
ourUuid.toString(),
|
||||
ourDeviceId
|
||||
);
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
new Address(ourUuid, ourDeviceId)
|
||||
);
|
||||
const senderKeyStore = new SenderKeys({ ourUuid });
|
||||
|
||||
return window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
||||
address,
|
||||
|
@ -2044,7 +2035,7 @@ export default class MessageSender {
|
|||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
const myUuid = window.textsecure.storage.user.getUuid();
|
||||
const myUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||
const recipients = groupIdentifiers.filter(
|
||||
identifier => identifier !== myNumber && identifier !== myUuid
|
||||
);
|
||||
|
|
5
ts/textsecure/Types.d.ts
vendored
5
ts/textsecure/Types.d.ts
vendored
|
@ -6,9 +6,14 @@ import type { IncomingWebSocketRequest } from './WebsocketResources';
|
|||
|
||||
export {
|
||||
IdentityKeyType,
|
||||
IdentityKeyIdType,
|
||||
PreKeyIdType,
|
||||
PreKeyType,
|
||||
SenderKeyIdType,
|
||||
SenderKeyType,
|
||||
SessionIdType,
|
||||
SessionType,
|
||||
SignedPreKeyIdType,
|
||||
SignedPreKeyType,
|
||||
UnprocessedType,
|
||||
UnprocessedUpdateType,
|
||||
|
|
|
@ -10,6 +10,9 @@ import {
|
|||
|
||||
import { UnregisteredUserError } from './Errors';
|
||||
import { Sessions, IdentityKeys } from '../LibSignalStores';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID } from '../types/UUID';
|
||||
import { ServerKeysType, WebAPIType } from './WebAPI';
|
||||
|
||||
export async function getKeysForIdentifier(
|
||||
|
@ -32,7 +35,11 @@ export async function getKeysForIdentifier(
|
|||
};
|
||||
} catch (error) {
|
||||
if (error.name === 'HTTPError' && error.code === 404) {
|
||||
await window.textsecure.storage.protocol.archiveAllSessions(identifier);
|
||||
const theirUuid = UUID.lookup(identifier);
|
||||
|
||||
if (theirUuid) {
|
||||
await window.textsecure.storage.protocol.archiveAllSessions(theirUuid);
|
||||
}
|
||||
}
|
||||
throw new UnregisteredUserError(identifier, error);
|
||||
}
|
||||
|
@ -72,8 +79,9 @@ async function handleServerKeys(
|
|||
response: ServerKeysType,
|
||||
devicesToUpdate?: Array<number>
|
||||
): Promise<void> {
|
||||
const sessionStore = new Sessions();
|
||||
const identityKeyStore = new IdentityKeys();
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const sessionStore = new Sessions({ ourUuid });
|
||||
const identityKeyStore = new IdentityKeys({ ourUuid });
|
||||
|
||||
await Promise.all(
|
||||
response.devices.map(async device => {
|
||||
|
@ -95,7 +103,11 @@ async function handleServerKeys(
|
|||
`getKeysForIdentifier/${identifier}: Missing signed prekey for deviceId ${deviceId}`
|
||||
);
|
||||
}
|
||||
const protocolAddress = ProtocolAddress.new(identifier, deviceId);
|
||||
const theirUuid = UUID.checkedLookup(identifier);
|
||||
const protocolAddress = ProtocolAddress.new(
|
||||
theirUuid.toString(),
|
||||
deviceId
|
||||
);
|
||||
const preKeyId = preKey?.keyId || null;
|
||||
const preKeyObject = preKey
|
||||
? PublicKey.deserialize(Buffer.from(preKey.publicKey))
|
||||
|
@ -118,7 +130,10 @@ async function handleServerKeys(
|
|||
identityKey
|
||||
);
|
||||
|
||||
const address = `${identifier}.${deviceId}`;
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
new Address(theirUuid, deviceId)
|
||||
);
|
||||
await window.textsecure.storage.protocol
|
||||
.enqueueSessionJob(address, () =>
|
||||
processPreKeyBundle(
|
||||
|
|
|
@ -5,6 +5,7 @@ import { WebAPICredentials } from '../Types.d';
|
|||
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { StorageInterface } from '../../types/Storage.d';
|
||||
import { UUID } from '../../types/UUID';
|
||||
|
||||
import Helpers from '../Helpers';
|
||||
|
||||
|
@ -56,10 +57,16 @@ export class User {
|
|||
return Helpers.unencodeNumber(numberId)[0];
|
||||
}
|
||||
|
||||
public getUuid(): string | undefined {
|
||||
public getUuid(): UUID | undefined {
|
||||
const uuid = this.storage.get('uuid_id');
|
||||
if (uuid === undefined) return undefined;
|
||||
return Helpers.unencodeNumber(uuid.toLowerCase())[0];
|
||||
return new UUID(Helpers.unencodeNumber(uuid.toLowerCase())[0]);
|
||||
}
|
||||
|
||||
public getCheckedUuid(): UUID {
|
||||
const uuid = this.getUuid();
|
||||
strictAssert(uuid !== undefined, 'Must have our own uuid');
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public getDeviceId(): number | undefined {
|
||||
|
|
30
ts/types/Address.ts
Normal file
30
ts/types/Address.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { strictAssert } from '../util/assert';
|
||||
|
||||
import { UUID, UUIDStringType } from './UUID';
|
||||
|
||||
export type AddressStringType = `${UUIDStringType}.${number}`;
|
||||
|
||||
const ADDRESS_REGEXP = /^([0-9a-f-]+).(\d+)$/i;
|
||||
|
||||
export class Address {
|
||||
constructor(public readonly uuid: UUID, public readonly deviceId: number) {}
|
||||
|
||||
public toString(): AddressStringType {
|
||||
return `${this.uuid.toString()}.${this.deviceId}`;
|
||||
}
|
||||
|
||||
public static parse(value: string): Address {
|
||||
const match = value.match(ADDRESS_REGEXP);
|
||||
strictAssert(match !== null, `Invalid Address: ${value}`);
|
||||
const [whole, uuid, deviceId] = match;
|
||||
strictAssert(whole === value, 'Integrity check');
|
||||
return Address.create(uuid, parseInt(deviceId, 10));
|
||||
}
|
||||
|
||||
public static create(uuid: string, deviceId: number): Address {
|
||||
return new Address(new UUID(uuid), deviceId);
|
||||
}
|
||||
}
|
48
ts/types/QualifiedAddress.ts
Normal file
48
ts/types/QualifiedAddress.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { strictAssert } from '../util/assert';
|
||||
|
||||
import { UUID, UUIDStringType } from './UUID';
|
||||
import { Address, AddressStringType } from './Address';
|
||||
|
||||
const QUALIFIED_ADDRESS_REGEXP = /^([0-9a-f-]+):([0-9a-f-]+).(\d+)$/i;
|
||||
|
||||
export type QualifiedAddressCreateOptionsType = Readonly<{
|
||||
ourUuid: string;
|
||||
uuid: string;
|
||||
deviceId: number;
|
||||
}>;
|
||||
|
||||
export type QualifiedAddressStringType = `${UUIDStringType}:${AddressStringType}`;
|
||||
|
||||
export class QualifiedAddress {
|
||||
constructor(
|
||||
public readonly ourUuid: UUID,
|
||||
public readonly address: Address
|
||||
) {}
|
||||
|
||||
public get uuid(): UUID {
|
||||
return this.address.uuid;
|
||||
}
|
||||
|
||||
public get deviceId(): number {
|
||||
return this.address.deviceId;
|
||||
}
|
||||
|
||||
public toString(): QualifiedAddressStringType {
|
||||
return `${this.ourUuid.toString()}:${this.address.toString()}`;
|
||||
}
|
||||
|
||||
public static parse(value: string): QualifiedAddress {
|
||||
const match = value.match(QUALIFIED_ADDRESS_REGEXP);
|
||||
strictAssert(match !== null, `Invalid QualifiedAddress: ${value}`);
|
||||
const [whole, ourUuid, uuid, deviceId] = match;
|
||||
strictAssert(whole === value, 'Integrity check');
|
||||
|
||||
return new QualifiedAddress(
|
||||
new UUID(ourUuid),
|
||||
Address.create(uuid, parseInt(deviceId, 10))
|
||||
);
|
||||
}
|
||||
}
|
12
ts/types/Storage.d.ts
vendored
12
ts/types/Storage.d.ts
vendored
|
@ -30,6 +30,14 @@ export type ThemeSettingType = 'system' | 'light' | 'dark';
|
|||
|
||||
export type NotificationSettingType = 'message' | 'name' | 'count' | 'off';
|
||||
|
||||
export type IdentityKeyMap = Record<
|
||||
string,
|
||||
{
|
||||
privKey: string;
|
||||
pubKey: string;
|
||||
}
|
||||
>;
|
||||
|
||||
// This should be in sync with `STORAGE_UI_KEYS` in `ts/types/StorageUIKeys.ts`.
|
||||
export type StorageAccessType = {
|
||||
'always-relay-calls': boolean;
|
||||
|
@ -55,7 +63,7 @@ export type StorageAccessType = {
|
|||
customColors: CustomColorsItemType;
|
||||
device_name: string;
|
||||
hasRegisterSupportForUnauthenticatedDelivery: boolean;
|
||||
identityKey: KeyPairType;
|
||||
identityKeyMap: IdentityKeyMap;
|
||||
lastHeartbeat: number;
|
||||
lastStartup: number;
|
||||
lastAttemptedToRefreshProfilesAt: number;
|
||||
|
@ -64,7 +72,7 @@ export type StorageAccessType = {
|
|||
password: string;
|
||||
profileKey: ArrayBuffer;
|
||||
regionCode: string;
|
||||
registrationId: number;
|
||||
registrationIdMap: Record<string, number>;
|
||||
remoteBuildExpiration: number;
|
||||
sessionResets: SessionResetsType;
|
||||
showStickerPickerHint: boolean;
|
||||
|
|
44
ts/types/UUID.ts
Normal file
44
ts/types/UUID.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { isValidGuid } from '../util/isValidGuid';
|
||||
|
||||
export type UUIDStringType = `${string}-${string}-${string}-${string}-${string}`;
|
||||
|
||||
export class UUID {
|
||||
constructor(protected readonly value: string) {
|
||||
strictAssert(isValidGuid(value), `Invalid UUID: ${value}`);
|
||||
}
|
||||
|
||||
public toString(): UUIDStringType {
|
||||
return (this.value as unknown) as UUIDStringType;
|
||||
}
|
||||
|
||||
public isEqual(other: UUID): boolean {
|
||||
return this.value === other.value;
|
||||
}
|
||||
|
||||
public static parse(value: string): UUID {
|
||||
return new UUID(value);
|
||||
}
|
||||
|
||||
public static lookup(identifier: string): UUID | undefined {
|
||||
const conversation = window.ConversationController.get(identifier);
|
||||
const uuid = conversation?.get('uuid');
|
||||
if (uuid === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new UUID(uuid);
|
||||
}
|
||||
|
||||
public static checkedLookup(identifier: string): UUID {
|
||||
const uuid = UUID.lookup(identifier);
|
||||
strictAssert(
|
||||
uuid !== undefined,
|
||||
`Conversation ${identifier} not found or has no uuid`
|
||||
);
|
||||
return uuid;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
import { ProfileKeyCredentialRequestContext } from 'zkgroup';
|
||||
import { SEALED_SENDER } from '../types/SealedSender';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID } from '../types/UUID';
|
||||
import {
|
||||
base64ToArrayBuffer,
|
||||
stringFromBytes,
|
||||
|
@ -54,6 +57,7 @@ export async function getProfile(
|
|||
const uuid = c.get('uuid')!;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const identifier = c.getSendTarget()!;
|
||||
const targetUuid = UUID.checkedLookup(identifier);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const profileKeyVersionHex = c.get('profileKeyVersion')!;
|
||||
const existingProfileKeyCredential = c.get('profileKeyCredential');
|
||||
|
@ -115,15 +119,16 @@ export async function getProfile(
|
|||
|
||||
const identityKey = base64ToArrayBuffer(profile.identityKey);
|
||||
const changed = await window.textsecure.storage.protocol.saveIdentity(
|
||||
`${identifier}.1`,
|
||||
new Address(targetUuid, 1),
|
||||
identityKey,
|
||||
false
|
||||
);
|
||||
if (changed) {
|
||||
// save identity will close all sessions except for .1, so we
|
||||
// must close that one manually.
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
await window.textsecure.storage.protocol.archiveSession(
|
||||
`${identifier}.1`
|
||||
new QualifiedAddress(ourUuid, new Address(targetUuid, 1))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ import { isGroupV2 } from './whatTypeOfConversation';
|
|||
import { isOlderThan } from './timestamp';
|
||||
import { parseIntOrThrow } from './parseIntOrThrow';
|
||||
import * as RemoteConfig from '../RemoteConfig';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
|
||||
import { ConversationModel } from '../models/conversations';
|
||||
import {
|
||||
|
@ -184,7 +186,11 @@ async function archiveSessionOnMatch({
|
|||
return;
|
||||
}
|
||||
|
||||
const address = `${requesterUuid}.${requesterDevice}`;
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
Address.create(requesterUuid, requesterDevice)
|
||||
);
|
||||
const session = await window.textsecure.storage.protocol.loadSession(address);
|
||||
|
||||
if (session && session.currentRatchetKeyMatches(ratchetKey)) {
|
||||
|
@ -500,9 +506,10 @@ function scheduleSessionReset(senderUuid: string, senderDevice: number) {
|
|||
}
|
||||
|
||||
lightSessionResetQueue.add(() => {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
window.textsecure.storage.protocol.lightSessionReset(
|
||||
senderUuid,
|
||||
senderDevice
|
||||
new QualifiedAddress(ourUuid, Address.create(senderUuid, senderDevice))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import { PublicKey, Fingerprint } from '@signalapp/signal-client';
|
||||
import { ConversationType } from '../state/ducks/conversations';
|
||||
import { UUID } from '../types/UUID';
|
||||
|
||||
import { assert } from './assert';
|
||||
|
||||
|
@ -33,16 +34,18 @@ export async function generateSecurityNumber(
|
|||
export async function generateSecurityNumberBlock(
|
||||
contact: ConversationType
|
||||
): Promise<Array<string>> {
|
||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
||||
const { storage } = window.textsecure;
|
||||
const ourNumber = storage.user.getNumber();
|
||||
const ourUuid = storage.user.getCheckedUuid();
|
||||
|
||||
const us = window.textsecure.storage.protocol.getIdentityRecord(
|
||||
ourUuid || ourNumber || ''
|
||||
);
|
||||
const us = storage.protocol.getIdentityRecord(ourUuid);
|
||||
const ourKey = us ? us.publicKey : null;
|
||||
|
||||
const them = window.textsecure.storage.protocol.getIdentityRecord(contact.id);
|
||||
const theirKey = them ? them.publicKey : null;
|
||||
const theirUuid = UUID.lookup(contact.id);
|
||||
const them = theirUuid
|
||||
? await storage.protocol.getOrMigrateIdentityRecord(theirUuid)
|
||||
: undefined;
|
||||
const theirKey = them?.publicKey;
|
||||
|
||||
if (!ourKey) {
|
||||
throw new Error('Could not load our key');
|
||||
|
|
|
@ -20,6 +20,9 @@ import {
|
|||
SenderCertificateMode,
|
||||
SendLogCallbackType,
|
||||
} from '../textsecure/OutgoingMessage';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID } from '../types/UUID';
|
||||
import { isEnabled } from '../RemoteConfig';
|
||||
|
||||
import { isOlderThan } from './timestamp';
|
||||
|
@ -286,10 +289,14 @@ export async function sendToGroupViaSenderKey(options: {
|
|||
}
|
||||
|
||||
// 2. Fetch all devices we believe we'll be sending to
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const {
|
||||
devices: currentDevices,
|
||||
emptyIdentifiers,
|
||||
} = await window.textsecure.storage.protocol.getOpenDevices(recipients);
|
||||
} = await window.textsecure.storage.protocol.getOpenDevices(
|
||||
ourUuid,
|
||||
recipients
|
||||
);
|
||||
|
||||
// 3. If we have no open sessions with people we believe we are sending to, and we
|
||||
// believe that any have signal accounts, fetch their prekey bundle and start
|
||||
|
@ -669,7 +676,13 @@ async function markIdentifierUnregistered(identifier: string) {
|
|||
conversation.setUnregistered();
|
||||
window.Signal.Data.updateConversation(conversation.attributes);
|
||||
|
||||
await window.textsecure.storage.protocol.archiveAllSessions(identifier);
|
||||
const uuid = UUID.lookup(identifier);
|
||||
if (!uuid) {
|
||||
window.log.warn(`No uuid found for ${identifier}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await window.textsecure.storage.protocol.archiveAllSessions(uuid);
|
||||
}
|
||||
|
||||
function isIdentifierRegistered(identifier: string) {
|
||||
|
@ -695,10 +708,13 @@ async function handle409Response(logId: string, error: Error) {
|
|||
|
||||
// Archive sessions with devices that have been removed
|
||||
if (devices.extraDevices && devices.extraDevices.length > 0) {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
await _waitForAll({
|
||||
tasks: devices.extraDevices.map(deviceId => async () => {
|
||||
const address = `${uuid}.${deviceId}`;
|
||||
await window.textsecure.storage.protocol.archiveSession(address);
|
||||
await window.textsecure.storage.protocol.archiveSession(
|
||||
new QualifiedAddress(ourUuid, Address.create(uuid, deviceId))
|
||||
);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -727,11 +743,14 @@ async function handle410Response(
|
|||
tasks: parsed.data.map(item => async () => {
|
||||
const { uuid, devices } = item;
|
||||
if (devices.staleDevices && devices.staleDevices.length > 0) {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
// First, archive our existing sessions with these devices
|
||||
await _waitForAll({
|
||||
tasks: devices.staleDevices.map(deviceId => async () => {
|
||||
const address = `${uuid}.${deviceId}`;
|
||||
await window.textsecure.storage.protocol.archiveSession(address);
|
||||
await window.textsecure.storage.protocol.archiveSession(
|
||||
new QualifiedAddress(ourUuid, Address.create(uuid, deviceId))
|
||||
);
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -822,24 +841,24 @@ async function encryptForSenderKey({
|
|||
distributionId: string;
|
||||
groupId: string;
|
||||
}): Promise<Buffer> {
|
||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||
if (!ourUuid || !ourDeviceId) {
|
||||
if (!ourDeviceId) {
|
||||
throw new Error(
|
||||
'encryptForSenderKey: Unable to fetch our uuid or deviceId'
|
||||
);
|
||||
}
|
||||
|
||||
const sender = ProtocolAddress.new(
|
||||
ourUuid,
|
||||
ourUuid.toString(),
|
||||
parseIntOrThrow(ourDeviceId, 'encryptForSenderKey, ourDeviceId')
|
||||
);
|
||||
const ourAddress = getOurAddress();
|
||||
const senderKeyStore = new SenderKeys();
|
||||
const senderKeyStore = new SenderKeys({ ourUuid });
|
||||
const message = Buffer.from(padMessage(new FIXMEU8(contentMessage)));
|
||||
|
||||
const ciphertextMessage = await window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
||||
ourAddress,
|
||||
new QualifiedAddress(ourUuid, ourAddress),
|
||||
() => groupEncrypt(sender, distributionId, senderKeyStore, message)
|
||||
);
|
||||
|
||||
|
@ -874,9 +893,14 @@ async function encryptForSenderKey({
|
|||
|
||||
return 1;
|
||||
})
|
||||
.map(device => ProtocolAddress.new(device.identifier, device.id));
|
||||
const identityKeyStore = new IdentityKeys();
|
||||
const sessionStore = new Sessions();
|
||||
.map(device => {
|
||||
return ProtocolAddress.new(
|
||||
UUID.checkedLookup(device.identifier).toString(),
|
||||
device.id
|
||||
);
|
||||
});
|
||||
const identityKeyStore = new IdentityKeys({ ourUuid });
|
||||
const sessionStore = new Sessions({ ourUuid });
|
||||
return sealedSenderMultiRecipientEncrypt(
|
||||
content,
|
||||
recipients,
|
||||
|
@ -998,13 +1022,13 @@ export function _analyzeSenderKeyDevices(
|
|||
};
|
||||
}
|
||||
|
||||
function getOurAddress(): string {
|
||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
||||
function getOurAddress(): Address {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||
if (!ourUuid || !ourDeviceId) {
|
||||
throw new Error('getOurAddress: Unable to fetch our uuid or deviceId');
|
||||
if (!ourDeviceId) {
|
||||
throw new Error('getOurAddress: Unable to fetch our deviceId');
|
||||
}
|
||||
return `${ourUuid}.${ourDeviceId}`;
|
||||
return new Address(ourUuid, ourDeviceId);
|
||||
}
|
||||
|
||||
async function resetSenderKey(conversation: ConversationModel): Promise<void> {
|
||||
|
@ -1023,7 +1047,7 @@ async function resetSenderKey(conversation: ConversationModel): Promise<void> {
|
|||
}
|
||||
|
||||
const { distributionId } = senderKeyInfo;
|
||||
const address = getOurAddress();
|
||||
const ourAddress = getOurAddress();
|
||||
|
||||
// Note: We preserve existing distributionId to minimize space for sender key storage
|
||||
conversation.set({
|
||||
|
@ -1035,8 +1059,9 @@ async function resetSenderKey(conversation: ConversationModel): Promise<void> {
|
|||
});
|
||||
window.Signal.Data.updateConversation(conversation.attributes);
|
||||
|
||||
const ourUuid = window.storage.user.getCheckedUuid();
|
||||
await window.textsecure.storage.protocol.removeSenderKey(
|
||||
address,
|
||||
new QualifiedAddress(ourUuid, ourAddress),
|
||||
distributionId
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ export function isDirectConversation(
|
|||
export function isMe(conversationAttrs: ConversationAttributesType): boolean {
|
||||
const { e164, uuid } = conversationAttrs;
|
||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
||||
const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||
return Boolean((e164 && e164 === ourNumber) || (uuid && uuid === ourUuid));
|
||||
}
|
||||
|
||||
|
|
7
ts/window.d.ts
vendored
7
ts/window.d.ts
vendored
|
@ -117,6 +117,9 @@ import { MessageController } from './util/MessageController';
|
|||
import { isValidGuid } from './util/isValidGuid';
|
||||
import { StateType } from './state/reducer';
|
||||
import { SystemTraySetting } from './types/SystemTraySetting';
|
||||
import { UUID } from './types/UUID';
|
||||
import { Address } from './types/Address';
|
||||
import { QualifiedAddress } from './types/QualifiedAddress';
|
||||
import { CI } from './CI';
|
||||
import { IPCEventsType } from './util/createIPCEvents';
|
||||
import { ConversationView } from './views/conversation_view';
|
||||
|
@ -396,6 +399,9 @@ declare global {
|
|||
path: string;
|
||||
};
|
||||
VisualAttachment: any;
|
||||
UUID: typeof UUID;
|
||||
Address: typeof Address;
|
||||
QualifiedAddress: typeof QualifiedAddress;
|
||||
};
|
||||
Util: typeof Util;
|
||||
GroupChange: {
|
||||
|
@ -595,7 +601,6 @@ export type WhisperType = {
|
|||
MessageCollection: typeof MessageModelCollectionType;
|
||||
|
||||
GroupMemberConversation: WhatIsThis;
|
||||
KeyChangeListener: WhatIsThis;
|
||||
RotateSignedPreKeyListener: WhatIsThis;
|
||||
WallClockListener: WhatIsThis;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue