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"
|
type="text/javascript"
|
||||||
src="js/rotate_signed_prekey_listener.js"
|
src="js/rotate_signed_prekey_listener.js"
|
||||||
></script>
|
></script>
|
||||||
<script type="text/javascript" src="js/keychange_listener.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="overflow-hidden">
|
<body class="overflow-hidden">
|
||||||
<div id="app-container">
|
<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 MessageType = require('./types/message');
|
||||||
const MIME = require('../../ts/types/MIME');
|
const MIME = require('../../ts/types/MIME');
|
||||||
const SettingsType = require('../../ts/types/Settings');
|
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
|
// Views
|
||||||
const Initialization = require('./views/initialization');
|
const Initialization = require('./views/initialization');
|
||||||
|
@ -430,6 +433,9 @@ exports.setup = (options = {}) => {
|
||||||
MIME,
|
MIME,
|
||||||
Settings: SettingsType,
|
Settings: SettingsType,
|
||||||
VisualAttachment,
|
VisualAttachment,
|
||||||
|
UUID,
|
||||||
|
Address,
|
||||||
|
QualifiedAddress,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Views = {
|
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="fake_web_api.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="test.js"></script>
|
<script type="text/javascript" src="test.js"></script>
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="in_memory_signal_protocol_store.js"
|
|
||||||
></script>
|
|
||||||
|
|
||||||
<script
|
<script
|
||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
|
@ -36,7 +32,6 @@
|
||||||
></script>
|
></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="helpers_test.js"></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="task_with_timeout_test.js"></script>
|
||||||
<script type="text/javascript" src="account_manager_test.js"></script>
|
<script type="text/javascript" src="account_manager_test.js"></script>
|
||||||
<script type="text/javascript" src="sendmessage_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 = {};
|
statistics = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ourUuid = window.textsecure.storage.user.getUuid();
|
||||||
|
|
||||||
event.sender.send('additional-log-data-response', {
|
event.sender.send('additional-log-data-response', {
|
||||||
capabilities: ourCapabilities || {},
|
capabilities: ourCapabilities || {},
|
||||||
remoteConfig: _.mapValues(remoteConfig, ({ value, enabled }) => {
|
remoteConfig: _.mapValues(remoteConfig, ({ value, enabled }) => {
|
||||||
|
@ -200,7 +202,7 @@ try {
|
||||||
user: {
|
user: {
|
||||||
deviceId: window.textsecure.storage.user.getDeviceId(),
|
deviceId: window.textsecure.storage.user.getDeviceId(),
|
||||||
e164: window.textsecure.storage.user.getNumber(),
|
e164: window.textsecure.storage.user.getNumber(),
|
||||||
uuid: window.textsecure.storage.user.getUuid(),
|
uuid: ourUuid && ourUuid.toString(),
|
||||||
conversationId: ourConversation && ourConversation.id,
|
conversationId: ourConversation && ourConversation.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -248,11 +248,6 @@
|
||||||
></script>
|
></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="../js/libphonenumber-util.js"></script>
|
<script type="text/javascript" src="../js/libphonenumber-util.js"></script>
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="../js/keychange_listener.js"
|
|
||||||
data-cover
|
|
||||||
></script>
|
|
||||||
<script
|
<script
|
||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
src="../js/expiring_messages.js"
|
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="views/list_view_test.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="libphonenumber_util_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="reliable_trigger_test.js"></script>
|
||||||
<script type="text/javascript" src="database_test.js"></script>
|
<script type="text/javascript" src="database_test.js"></script>
|
||||||
<script type="text/javascript" src="i18n_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 dataInterface from './sql/Client';
|
||||||
import {
|
import {
|
||||||
ConversationModelCollectionType,
|
ConversationModelCollectionType,
|
||||||
|
ConversationAttributesType,
|
||||||
ConversationAttributesTypeType,
|
ConversationAttributesTypeType,
|
||||||
} from './model-types.d';
|
} from './model-types.d';
|
||||||
import { ConversationModel } from './models/conversations';
|
import { ConversationModel } from './models/conversations';
|
||||||
|
@ -15,6 +16,9 @@ import { assert } from './util/assert';
|
||||||
import { isValidGuid } from './util/isValidGuid';
|
import { isValidGuid } from './util/isValidGuid';
|
||||||
import { map, reduce } from './util/iterables';
|
import { map, reduce } from './util/iterables';
|
||||||
import { isGroupV1, isGroupV2 } from './util/whatTypeOfConversation';
|
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;
|
const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
|
||||||
|
|
||||||
|
@ -156,7 +160,7 @@ export class ConversationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
dangerouslyCreateAndAdd(
|
dangerouslyCreateAndAdd(
|
||||||
attributes: Partial<ConversationModel>
|
attributes: Partial<ConversationAttributesType>
|
||||||
): ConversationModel {
|
): ConversationModel {
|
||||||
return this._conversations.add(attributes);
|
return this._conversations.add(attributes);
|
||||||
}
|
}
|
||||||
|
@ -295,7 +299,7 @@ export class ConversationController {
|
||||||
|
|
||||||
getOurConversationId(): string | undefined {
|
getOurConversationId(): string | undefined {
|
||||||
const e164 = window.textsecure.storage.user.getNumber();
|
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 });
|
return this.ensureContactIds({ e164, uuid, highTrust: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,13 +643,14 @@ export class ConversationController {
|
||||||
}
|
}
|
||||||
|
|
||||||
const obsoleteId = obsolete.get('id');
|
const obsoleteId = obsolete.get('id');
|
||||||
|
const obsoleteUuid = obsolete.get('uuid');
|
||||||
const currentId = current.get('id');
|
const currentId = current.get('id');
|
||||||
window.log.warn('combineConversations: Combining two conversations', {
|
window.log.warn('combineConversations: Combining two conversations', {
|
||||||
obsolete: obsoleteId,
|
obsolete: obsoleteId,
|
||||||
current: currentId,
|
current: currentId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (conversationType === 'private') {
|
if (conversationType === 'private' && obsoleteUuid) {
|
||||||
if (!current.get('profileKey') && obsolete.get('profileKey')) {
|
if (!current.get('profileKey') && obsolete.get('profileKey')) {
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
'combineConversations: Copying profile key from old to new contact'
|
'combineConversations: Copying profile key from old to new contact'
|
||||||
|
@ -661,21 +666,30 @@ export class ConversationController {
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
'combineConversations: Delete all sessions tied to old conversationId'
|
'combineConversations: Delete all sessions tied to old conversationId'
|
||||||
);
|
);
|
||||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds(
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
obsoleteId
|
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
|
||||||
);
|
ourUuid,
|
||||||
|
identifier: obsoleteUuid,
|
||||||
|
});
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
deviceIds.map(async deviceId => {
|
deviceIds.map(async deviceId => {
|
||||||
await window.textsecure.storage.protocol.removeSession(
|
const addr = new QualifiedAddress(
|
||||||
`${obsoleteId}.${deviceId}`
|
ourUuid,
|
||||||
|
Address.create(obsoleteUuid, deviceId)
|
||||||
);
|
);
|
||||||
|
await window.textsecure.storage.protocol.removeSession(addr);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
'combineConversations: Delete all identity information tied to old conversationId'
|
'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(
|
window.log.warn(
|
||||||
'combineConversations: Ensure that all V1 groups have new conversationId instead of old'
|
'combineConversations: Ensure that all V1 groups have new conversationId instead of old'
|
||||||
|
|
|
@ -23,28 +23,42 @@ import {
|
||||||
Uuid,
|
Uuid,
|
||||||
} from '@signalapp/signal-client';
|
} from '@signalapp/signal-client';
|
||||||
import { freezePreKey, freezeSignedPreKey } from './SignalProtocolStore';
|
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 { typedArrayToArrayBuffer } from './Crypto';
|
||||||
|
|
||||||
import { Zone } from './util/Zone';
|
import { Zone } from './util/Zone';
|
||||||
|
|
||||||
function encodedNameFromAddress(address: ProtocolAddress): string {
|
function encodeAddress(address: ProtocolAddress): Address {
|
||||||
const name = address.name();
|
const name = address.name();
|
||||||
const deviceId = address.deviceId();
|
const deviceId = address.deviceId();
|
||||||
const encodedName = `${name}.${deviceId}`;
|
return Address.create(name, deviceId);
|
||||||
return encodedName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SessionsOptions = {
|
function toQualifiedAddress(
|
||||||
readonly zone?: Zone;
|
ourUuid: UUID,
|
||||||
};
|
address: ProtocolAddress
|
||||||
|
): QualifiedAddress {
|
||||||
|
return new QualifiedAddress(ourUuid, encodeAddress(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionsOptions = Readonly<{
|
||||||
|
ourUuid: UUID;
|
||||||
|
zone?: Zone;
|
||||||
|
}>;
|
||||||
|
|
||||||
export class Sessions extends SessionStore {
|
export class Sessions extends SessionStore {
|
||||||
|
private readonly ourUuid: UUID;
|
||||||
|
|
||||||
private readonly zone: Zone | undefined;
|
private readonly zone: Zone | undefined;
|
||||||
|
|
||||||
constructor(options: SessionsOptions = {}) {
|
constructor({ ourUuid, zone }: SessionsOptions) {
|
||||||
super();
|
super();
|
||||||
this.zone = options.zone;
|
|
||||||
|
this.ourUuid = ourUuid;
|
||||||
|
this.zone = zone;
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveSession(
|
async saveSession(
|
||||||
|
@ -52,16 +66,16 @@ export class Sessions extends SessionStore {
|
||||||
record: SessionRecord
|
record: SessionRecord
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await window.textsecure.storage.protocol.storeSession(
|
await window.textsecure.storage.protocol.storeSession(
|
||||||
encodedNameFromAddress(address),
|
toQualifiedAddress(this.ourUuid, address),
|
||||||
record,
|
record,
|
||||||
{ zone: this.zone }
|
{ zone: this.zone }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSession(name: ProtocolAddress): Promise<SessionRecord | null> {
|
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(
|
const record = await window.textsecure.storage.protocol.loadSession(
|
||||||
encodedName,
|
encodedAddress,
|
||||||
{ zone: this.zone }
|
{ zone: this.zone }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -71,27 +85,36 @@ export class Sessions extends SessionStore {
|
||||||
async getExistingSessions(
|
async getExistingSessions(
|
||||||
addresses: Array<ProtocolAddress>
|
addresses: Array<ProtocolAddress>
|
||||||
): Promise<Array<SessionRecord>> {
|
): Promise<Array<SessionRecord>> {
|
||||||
const encodedAddresses = addresses.map(encodedNameFromAddress);
|
const encodedAddresses = addresses.map(addr =>
|
||||||
|
toQualifiedAddress(this.ourUuid, addr)
|
||||||
|
);
|
||||||
return window.textsecure.storage.protocol.loadSessions(encodedAddresses, {
|
return window.textsecure.storage.protocol.loadSessions(encodedAddresses, {
|
||||||
zone: this.zone,
|
zone: this.zone,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IdentityKeysOptions = {
|
export type IdentityKeysOptions = Readonly<{
|
||||||
readonly zone?: Zone;
|
ourUuid: UUID;
|
||||||
};
|
zone?: Zone;
|
||||||
|
}>;
|
||||||
|
|
||||||
export class IdentityKeys extends IdentityKeyStore {
|
export class IdentityKeys extends IdentityKeyStore {
|
||||||
|
private readonly ourUuid: UUID;
|
||||||
|
|
||||||
private readonly zone: Zone | undefined;
|
private readonly zone: Zone | undefined;
|
||||||
|
|
||||||
constructor({ zone }: IdentityKeysOptions = {}) {
|
constructor({ ourUuid, zone }: IdentityKeysOptions) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.ourUuid = ourUuid;
|
||||||
this.zone = zone;
|
this.zone = zone;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getIdentityKey(): Promise<PrivateKey> {
|
async getIdentityKey(): Promise<PrivateKey> {
|
||||||
const keyPair = await window.textsecure.storage.protocol.getIdentityKeyPair();
|
const keyPair = await window.textsecure.storage.protocol.getIdentityKeyPair(
|
||||||
|
this.ourUuid
|
||||||
|
);
|
||||||
if (!keyPair) {
|
if (!keyPair) {
|
||||||
throw new Error('IdentityKeyStore/getIdentityKey: No identity key!');
|
throw new Error('IdentityKeyStore/getIdentityKey: No identity key!');
|
||||||
}
|
}
|
||||||
|
@ -99,7 +122,9 @@ export class IdentityKeys extends IdentityKeyStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLocalRegistrationId(): Promise<number> {
|
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)) {
|
if (!isNumber(id)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'IdentityKeyStore/getLocalRegistrationId: No registration id!'
|
'IdentityKeyStore/getLocalRegistrationId: No registration id!'
|
||||||
|
@ -109,9 +134,9 @@ export class IdentityKeys extends IdentityKeyStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getIdentity(address: ProtocolAddress): Promise<PublicKey | null> {
|
async getIdentity(address: ProtocolAddress): Promise<PublicKey | null> {
|
||||||
const encodedName = encodedNameFromAddress(address);
|
const encodedAddress = encodeAddress(address);
|
||||||
const key = await window.textsecure.storage.protocol.loadIdentityKey(
|
const key = await window.textsecure.storage.protocol.loadIdentityKey(
|
||||||
encodedName
|
encodedAddress.uuid
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!key) {
|
if (!key) {
|
||||||
|
@ -122,13 +147,13 @@ export class IdentityKeys extends IdentityKeyStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveIdentity(name: ProtocolAddress, key: PublicKey): Promise<boolean> {
|
async saveIdentity(name: ProtocolAddress, key: PublicKey): Promise<boolean> {
|
||||||
const encodedName = encodedNameFromAddress(name);
|
const encodedAddress = encodeAddress(name);
|
||||||
const publicKey = typedArrayToArrayBuffer(key.serialize());
|
const publicKey = typedArrayToArrayBuffer(key.serialize());
|
||||||
|
|
||||||
// Pass `zone` to let `saveIdentity` archive sibling sessions when identity
|
// Pass `zone` to let `saveIdentity` archive sibling sessions when identity
|
||||||
// key changes.
|
// key changes.
|
||||||
return window.textsecure.storage.protocol.saveIdentity(
|
return window.textsecure.storage.protocol.saveIdentity(
|
||||||
encodedName,
|
encodedAddress,
|
||||||
publicKey,
|
publicKey,
|
||||||
false,
|
false,
|
||||||
{ zone: this.zone }
|
{ zone: this.zone }
|
||||||
|
@ -140,27 +165,42 @@ export class IdentityKeys extends IdentityKeyStore {
|
||||||
key: PublicKey,
|
key: PublicKey,
|
||||||
direction: Direction
|
direction: Direction
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const encodedName = encodedNameFromAddress(name);
|
const encodedAddress = encodeAddress(name);
|
||||||
const publicKey = typedArrayToArrayBuffer(key.serialize());
|
const publicKey = typedArrayToArrayBuffer(key.serialize());
|
||||||
|
|
||||||
return window.textsecure.storage.protocol.isTrustedIdentity(
|
return window.textsecure.storage.protocol.isTrustedIdentity(
|
||||||
encodedName,
|
encodedAddress,
|
||||||
publicKey,
|
publicKey,
|
||||||
direction
|
direction
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PreKeysOptions = Readonly<{
|
||||||
|
ourUuid: UUID;
|
||||||
|
}>;
|
||||||
|
|
||||||
export class PreKeys extends PreKeyStore {
|
export class PreKeys extends PreKeyStore {
|
||||||
|
private readonly ourUuid: UUID;
|
||||||
|
|
||||||
|
constructor({ ourUuid }: PreKeysOptions) {
|
||||||
|
super();
|
||||||
|
this.ourUuid = ourUuid;
|
||||||
|
}
|
||||||
|
|
||||||
async savePreKey(id: number, record: PreKeyRecord): Promise<void> {
|
async savePreKey(id: number, record: PreKeyRecord): Promise<void> {
|
||||||
await window.textsecure.storage.protocol.storePreKey(
|
await window.textsecure.storage.protocol.storePreKey(
|
||||||
|
this.ourUuid,
|
||||||
id,
|
id,
|
||||||
freezePreKey(record)
|
freezePreKey(record)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPreKey(id: number): Promise<PreKeyRecord> {
|
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) {
|
if (preKey === undefined) {
|
||||||
throw new Error(`getPreKey: PreKey ${id} not found`);
|
throw new Error(`getPreKey: PreKey ${id} not found`);
|
||||||
|
@ -170,17 +210,28 @@ export class PreKeys extends PreKeyStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
async removePreKey(id: number): Promise<void> {
|
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 {
|
export class SenderKeys extends SenderKeyStore {
|
||||||
|
private readonly ourUuid: UUID;
|
||||||
|
|
||||||
|
constructor({ ourUuid }: SenderKeysOptions) {
|
||||||
|
super();
|
||||||
|
this.ourUuid = ourUuid;
|
||||||
|
}
|
||||||
|
|
||||||
async saveSenderKey(
|
async saveSenderKey(
|
||||||
sender: ProtocolAddress,
|
sender: ProtocolAddress,
|
||||||
distributionId: Uuid,
|
distributionId: Uuid,
|
||||||
record: SenderKeyRecord
|
record: SenderKeyRecord
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const encodedAddress = encodedNameFromAddress(sender);
|
const encodedAddress = toQualifiedAddress(this.ourUuid, sender);
|
||||||
|
|
||||||
await window.textsecure.storage.protocol.saveSenderKey(
|
await window.textsecure.storage.protocol.saveSenderKey(
|
||||||
encodedAddress,
|
encodedAddress,
|
||||||
|
@ -193,7 +244,7 @@ export class SenderKeys extends SenderKeyStore {
|
||||||
sender: ProtocolAddress,
|
sender: ProtocolAddress,
|
||||||
distributionId: Uuid
|
distributionId: Uuid
|
||||||
): Promise<SenderKeyRecord | null> {
|
): Promise<SenderKeyRecord | null> {
|
||||||
const encodedAddress = encodedNameFromAddress(sender);
|
const encodedAddress = toQualifiedAddress(this.ourUuid, sender);
|
||||||
|
|
||||||
const senderKey = await window.textsecure.storage.protocol.getSenderKey(
|
const senderKey = await window.textsecure.storage.protocol.getSenderKey(
|
||||||
encodedAddress,
|
encodedAddress,
|
||||||
|
@ -204,12 +255,24 @@ export class SenderKeys extends SenderKeyStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SignedPreKeysOptions = Readonly<{
|
||||||
|
ourUuid: UUID;
|
||||||
|
}>;
|
||||||
|
|
||||||
export class SignedPreKeys extends SignedPreKeyStore {
|
export class SignedPreKeys extends SignedPreKeyStore {
|
||||||
|
private readonly ourUuid: UUID;
|
||||||
|
|
||||||
|
constructor({ ourUuid }: SignedPreKeysOptions) {
|
||||||
|
super();
|
||||||
|
this.ourUuid = ourUuid;
|
||||||
|
}
|
||||||
|
|
||||||
async saveSignedPreKey(
|
async saveSignedPreKey(
|
||||||
id: number,
|
id: number,
|
||||||
record: SignedPreKeyRecord
|
record: SignedPreKeyRecord
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await window.textsecure.storage.protocol.storeSignedPreKey(
|
await window.textsecure.storage.protocol.storeSignedPreKey(
|
||||||
|
this.ourUuid,
|
||||||
id,
|
id,
|
||||||
freezeSignedPreKey(record),
|
freezeSignedPreKey(record),
|
||||||
true
|
true
|
||||||
|
@ -218,6 +281,7 @@ export class SignedPreKeys extends SignedPreKeyStore {
|
||||||
|
|
||||||
async getSignedPreKey(id: number): Promise<SignedPreKeyRecord> {
|
async getSignedPreKey(id: number): Promise<SignedPreKeyRecord> {
|
||||||
const signedPreKey = await window.textsecure.storage.protocol.loadSignedPreKey(
|
const signedPreKey = await window.textsecure.storage.protocol.loadSignedPreKey(
|
||||||
|
this.ourUuid,
|
||||||
id
|
id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -66,6 +66,7 @@ import {
|
||||||
EnvelopeEvent,
|
EnvelopeEvent,
|
||||||
} from './textsecure/messageReceiverEvents';
|
} from './textsecure/messageReceiverEvents';
|
||||||
import type { WebAPIType } from './textsecure/WebAPI';
|
import type { WebAPIType } from './textsecure/WebAPI';
|
||||||
|
import * as KeyChangeListener from './textsecure/KeyChangeListener';
|
||||||
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
||||||
import { getSendOptions } from './util/getSendOptions';
|
import { getSendOptions } from './util/getSendOptions';
|
||||||
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
|
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
|
||||||
|
@ -497,7 +498,7 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
window.document.title = window.getTitle();
|
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.textsecure.storage.protocol.on('removePreKey', () => {
|
||||||
window.getAccountManager().refreshPreKeys();
|
window.getAccountManager().refreshPreKeys();
|
||||||
});
|
});
|
||||||
|
@ -921,7 +922,7 @@ export async function startApp(): Promise<void> {
|
||||||
conversation.format()
|
conversation.format()
|
||||||
);
|
);
|
||||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
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 ourConversationId = window.ConversationController.getOurConversationId();
|
||||||
|
|
||||||
const themeSetting = window.Events.getThemeSetting();
|
const themeSetting = window.Events.getThemeSetting();
|
||||||
|
@ -1065,7 +1066,7 @@ export async function startApp(): Promise<void> {
|
||||||
window.Whisper.events.on('userChanged', (reconnect = false) => {
|
window.Whisper.events.on('userChanged', (reconnect = false) => {
|
||||||
const newDeviceId = window.textsecure.storage.user.getDeviceId();
|
const newDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||||
const newNumber = window.textsecure.storage.user.getNumber();
|
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();
|
const ourConversation = window.ConversationController.getOurConversation();
|
||||||
|
|
||||||
if (ourConversation?.get('e164') !== newNumber) {
|
if (ourConversation?.get('e164') !== newNumber) {
|
||||||
|
@ -2136,30 +2137,9 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
const deviceId = window.textsecure.storage.user.getDeviceId();
|
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()) {
|
if (!window.textsecure.storage.user.getUuid()) {
|
||||||
try {
|
window.log.error('UUID not captured during registration, unlinking');
|
||||||
const { uuid } = await server.whoami();
|
return unlinkAndDisconnect(RemoveAllConfiguration.Full);
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectCount === 1) {
|
if (connectCount === 1) {
|
||||||
|
@ -2587,7 +2567,7 @@ export async function startApp(): Promise<void> {
|
||||||
(details.number &&
|
(details.number &&
|
||||||
details.number === window.textsecure.storage.user.getNumber()) ||
|
details.number === window.textsecure.storage.user.getNumber()) ||
|
||||||
(details.uuid &&
|
(details.uuid &&
|
||||||
details.uuid === window.textsecure.storage.user.getUuid())
|
details.uuid === window.textsecure.storage.user.getUuid()?.toString())
|
||||||
) {
|
) {
|
||||||
// special case for syncing details about ourselves
|
// special case for syncing details about ourselves
|
||||||
if (details.profileKey) {
|
if (details.profileKey) {
|
||||||
|
@ -2845,7 +2825,7 @@ export async function startApp(): Promise<void> {
|
||||||
});
|
});
|
||||||
|
|
||||||
function onEnvelopeReceived({ envelope }: EnvelopeEvent) {
|
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) {
|
if (envelope.sourceUuid && envelope.sourceUuid !== ourUuid) {
|
||||||
window.ConversationController.ensureContactIds({
|
window.ConversationController.ensureContactIds({
|
||||||
e164: envelope.source,
|
e164: envelope.source,
|
||||||
|
@ -3083,7 +3063,7 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
return new window.Whisper.Message(({
|
return new window.Whisper.Message(({
|
||||||
source: window.textsecure.storage.user.getNumber(),
|
source: window.textsecure.storage.user.getNumber(),
|
||||||
sourceUuid: window.textsecure.storage.user.getUuid(),
|
sourceUuid: window.textsecure.storage.user.getUuid()?.toString(),
|
||||||
sourceDevice: data.device,
|
sourceDevice: data.device,
|
||||||
sent_at: timestamp,
|
sent_at: timestamp,
|
||||||
serverTimestamp: data.serverTimestamp,
|
serverTimestamp: data.serverTimestamp,
|
||||||
|
@ -3216,7 +3196,7 @@ export async function startApp(): Promise<void> {
|
||||||
const { data, confirm } = event;
|
const { data, confirm } = event;
|
||||||
|
|
||||||
const source = window.textsecure.storage.user.getNumber();
|
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');
|
strictAssert(source && sourceUuid, 'Missing user number and uuid');
|
||||||
|
|
||||||
const messageDescriptor = getMessageDescriptor({
|
const messageDescriptor = getMessageDescriptor({
|
||||||
|
@ -3492,7 +3472,7 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case FETCH_LATEST_ENUM.LOCAL_PROFILE: {
|
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();
|
const ourE164 = window.textsecure.storage.user.getNumber();
|
||||||
await getProfile(ourUuid, ourE164);
|
await getProfile(ourUuid, ourE164);
|
||||||
break;
|
break;
|
||||||
|
|
35
ts/groups.ts
35
ts/groups.ts
|
@ -550,19 +550,12 @@ function buildGroupProto(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const me = window.ConversationController.get(ourConversationId);
|
const ourUuid = window.storage.user.getCheckedUuid();
|
||||||
if (!me) {
|
|
||||||
throw new Error(
|
|
||||||
`buildGroupProto/${logId}: unable to find our own conversation!`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ourUuid = me.get('uuid');
|
const ourUuidCipherTextBuffer = encryptUuid(
|
||||||
if (!ourUuid) {
|
clientZkGroupCipher,
|
||||||
throw new Error(`buildGroupProto/${logId}: unable to find our own uuid!`);
|
ourUuid.toString()
|
||||||
}
|
);
|
||||||
|
|
||||||
const ourUuidCipherTextBuffer = encryptUuid(clientZkGroupCipher, ourUuid);
|
|
||||||
|
|
||||||
proto.membersPendingProfileKey = (attributes.pendingMembersV2 || []).map(
|
proto.membersPendingProfileKey = (attributes.pendingMembersV2 || []).map(
|
||||||
item => {
|
item => {
|
||||||
|
@ -627,15 +620,11 @@ export async function buildAddMembersChange(
|
||||||
);
|
);
|
||||||
const clientZkGroupCipher = getClientZkGroupCipher(secretParams);
|
const clientZkGroupCipher = getClientZkGroupCipher(secretParams);
|
||||||
|
|
||||||
const ourConversationId = window.ConversationController.getOurConversationIdOrThrow();
|
const ourUuid = window.storage.user.getCheckedUuid();
|
||||||
const ourConversation = window.ConversationController.get(ourConversationId);
|
const ourUuidCipherTextBuffer = encryptUuid(
|
||||||
const ourUuid = ourConversation?.get('uuid');
|
clientZkGroupCipher,
|
||||||
if (!ourUuid) {
|
ourUuid.toString()
|
||||||
throw new Error(
|
);
|
||||||
`buildAddMembersChange/${logId}: unable to find our own UUID!`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const ourUuidCipherTextBuffer = encryptUuid(clientZkGroupCipher, ourUuid);
|
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
@ -1727,10 +1716,12 @@ export async function createGroupV2({
|
||||||
timestamp,
|
timestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ourUuid = window.storage.user.getCheckedUuid();
|
||||||
|
|
||||||
const createdTheGroupMessage: MessageAttributesType = {
|
const createdTheGroupMessage: MessageAttributesType = {
|
||||||
...generateBasicMessage(),
|
...generateBasicMessage(),
|
||||||
type: 'group-v2-change',
|
type: 'group-v2-change',
|
||||||
sourceUuid: conversation.ourUuid,
|
sourceUuid: ourUuid.toString(),
|
||||||
conversationId: conversation.id,
|
conversationId: conversation.id,
|
||||||
received_at: window.Signal.Util.incrementMessageCounter(),
|
received_at: window.Signal.Util.incrementMessageCounter(),
|
||||||
received_at_ms: timestamp,
|
received_at_ms: timestamp,
|
||||||
|
|
|
@ -37,6 +37,7 @@ import { missingCaseError } from '../util/missingCaseError';
|
||||||
import { sniffImageMimeType } from '../util/sniffImageMimeType';
|
import { sniffImageMimeType } from '../util/sniffImageMimeType';
|
||||||
import { isValidE164 } from '../util/isValidE164';
|
import { isValidE164 } from '../util/isValidE164';
|
||||||
import { MIMEType, IMAGE_WEBP } from '../types/MIME';
|
import { MIMEType, IMAGE_WEBP } from '../types/MIME';
|
||||||
|
import { UUID } from '../types/UUID';
|
||||||
import {
|
import {
|
||||||
arrayBufferToBase64,
|
arrayBufferToBase64,
|
||||||
base64ToArrayBuffer,
|
base64ToArrayBuffer,
|
||||||
|
@ -157,8 +158,6 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
jobQueue?: typeof window.PQueueType;
|
jobQueue?: typeof window.PQueueType;
|
||||||
|
|
||||||
ourUuid?: string;
|
|
||||||
|
|
||||||
storeName?: string | null;
|
storeName?: string | null;
|
||||||
|
|
||||||
throttledBumpTyping?: () => void;
|
throttledBumpTyping?: () => void;
|
||||||
|
@ -239,7 +238,6 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
this.storeName = 'conversations';
|
this.storeName = 'conversations';
|
||||||
|
|
||||||
this.ourUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
this.verifiedEnum = window.textsecure.storage.protocol.VerifiedStatus;
|
this.verifiedEnum = window.textsecure.storage.protocol.VerifiedStatus;
|
||||||
|
|
||||||
// This may be overridden by window.ConversationController.getOrCreate, and signify
|
// This may be overridden by window.ConversationController.getOrCreate, and signify
|
||||||
|
@ -2105,7 +2103,14 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
async safeGetVerified(): Promise<number> {
|
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(
|
return promise.catch(
|
||||||
() => window.textsecure.storage.protocol.VerifiedStatus.DEFAULT
|
() => 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');
|
const beginningVerified = this.get('verified');
|
||||||
let keyChange;
|
let keyChange;
|
||||||
if (options.viaSyncMessage) {
|
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
|
// handle the incoming key from the sync messages - need different
|
||||||
// behavior if that key doesn't match the current key
|
// behavior if that key doesn't match the current key
|
||||||
keyChange = await window.textsecure.storage.protocol.processVerifiedMessage(
|
keyChange = await window.textsecure.storage.protocol.processVerifiedMessage(
|
||||||
this.id,
|
new UUID(uuid),
|
||||||
verified,
|
verified,
|
||||||
options.key || undefined
|
options.key || undefined
|
||||||
);
|
);
|
||||||
} else {
|
} else if (uuid) {
|
||||||
keyChange = await window.textsecure.storage.protocol.setVerified(
|
keyChange = await window.textsecure.storage.protocol.setVerified(
|
||||||
this.id,
|
new UUID(uuid),
|
||||||
verified
|
verified
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
window.log.warn(
|
||||||
|
`_setVerified(${this.id}): no uuid to update protocol storage`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set({ verified });
|
this.set({ verified });
|
||||||
|
@ -2227,12 +2242,8 @@ export class ConversationModel extends window.Backbone
|
||||||
local: !options.viaSyncMessage,
|
local: !options.viaSyncMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!options.viaSyncMessage) {
|
if (!options.viaSyncMessage && uuid) {
|
||||||
await this.sendVerifySyncMessage(
|
await this.sendVerifySyncMessage(this.get('e164'), uuid, verified);
|
||||||
this.get('e164'),
|
|
||||||
this.get('uuid'),
|
|
||||||
verified
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyChange;
|
return keyChange;
|
||||||
|
@ -2240,7 +2251,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
async sendVerifySyncMessage(
|
async sendVerifySyncMessage(
|
||||||
e164: string | undefined,
|
e164: string | undefined,
|
||||||
uuid: string | undefined,
|
uuid: string,
|
||||||
state: number
|
state: number
|
||||||
): Promise<CallbackResultType | void> {
|
): Promise<CallbackResultType | void> {
|
||||||
const identifier = uuid || e164;
|
const identifier = uuid || e164;
|
||||||
|
@ -2268,7 +2279,7 @@ export class ConversationModel extends window.Backbone
|
||||||
const options = { ...sendOptions, ...contactSendOptions };
|
const options = { ...sendOptions, ...contactSendOptions };
|
||||||
|
|
||||||
const key = await window.textsecure.storage.protocol.loadIdentityKey(
|
const key = await window.textsecure.storage.protocol.loadIdentityKey(
|
||||||
identifier
|
UUID.checkedLookup(identifier)
|
||||||
);
|
);
|
||||||
if (!key) {
|
if (!key) {
|
||||||
throw new Error(
|
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 {
|
safeIsUntrusted(): boolean {
|
||||||
|
const uuid = this.get('uuid');
|
||||||
try {
|
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) {
|
} catch (err) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2526,11 +2545,11 @@ export class ConversationModel extends window.Backbone
|
||||||
await this.notify(model);
|
await this.notify(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addKeyChange(keyChangedId: string): Promise<void> {
|
async addKeyChange(keyChangedId: UUID): Promise<void> {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'adding key change advisory for',
|
'adding key change advisory for',
|
||||||
this.idForLogging(),
|
this.idForLogging(),
|
||||||
keyChangedId,
|
keyChangedId.toString(),
|
||||||
this.get('timestamp')
|
this.get('timestamp')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2541,7 +2560,7 @@ export class ConversationModel extends window.Backbone
|
||||||
sent_at: this.get('timestamp'),
|
sent_at: this.get('timestamp'),
|
||||||
received_at: window.Signal.Util.incrementMessageCounter(),
|
received_at: window.Signal.Util.incrementMessageCounter(),
|
||||||
received_at_ms: timestamp,
|
received_at_ms: timestamp,
|
||||||
key_changed: keyChangedId,
|
key_changed: keyChangedId.toString(),
|
||||||
readStatus: ReadStatus.Unread,
|
readStatus: ReadStatus.Unread,
|
||||||
schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY,
|
schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY,
|
||||||
// TODO: DESKTOP-722
|
// TODO: DESKTOP-722
|
||||||
|
@ -4839,7 +4858,7 @@ export class ConversationModel extends window.Backbone
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||||
const mentionsMe = (message.get('bodyRanges') || []).some(
|
const mentionsMe = (message.get('bodyRanges') || []).some(
|
||||||
range => range.mentionUuid && range.mentionUuid === ourUuid
|
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.CURRENT_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.CURRENT;
|
||||||
this.INITIAL_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.INITIAL;
|
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);
|
this.on('change', this.notifyRedux);
|
||||||
}
|
}
|
||||||
|
@ -2881,7 +2881,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
const profileKey = dataMessage.profileKey.toString('base64');
|
const profileKey = dataMessage.profileKey.toString('base64');
|
||||||
if (
|
if (
|
||||||
source === window.textsecure.storage.user.getNumber() ||
|
source === window.textsecure.storage.user.getNumber() ||
|
||||||
sourceUuid === window.textsecure.storage.user.getUuid()
|
sourceUuid ===
|
||||||
|
window.textsecure.storage.user.getUuid()?.toString()
|
||||||
) {
|
) {
|
||||||
conversation.set({ profileSharing: true });
|
conversation.set({ profileSharing: true });
|
||||||
} else if (isDirectConversation(conversation.attributes)) {
|
} else if (isDirectConversation(conversation.attributes)) {
|
||||||
|
|
|
@ -53,6 +53,7 @@ import {
|
||||||
ProcessGroupCallRingRequestResult,
|
ProcessGroupCallRingRequestResult,
|
||||||
} from '../types/Calling';
|
} from '../types/Calling';
|
||||||
import { LocalizerType } from '../types/Util';
|
import { LocalizerType } from '../types/Util';
|
||||||
|
import { UUID } from '../types/UUID';
|
||||||
import { ConversationModel } from '../models/conversations';
|
import { ConversationModel } from '../models/conversations';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
import {
|
import {
|
||||||
|
@ -60,7 +61,6 @@ import {
|
||||||
arrayBufferToUuid,
|
arrayBufferToUuid,
|
||||||
typedArrayToArrayBuffer,
|
typedArrayToArrayBuffer,
|
||||||
} from '../Crypto';
|
} from '../Crypto';
|
||||||
import { assert } from '../util/assert';
|
|
||||||
import { dropNull, shallowDropNull } from '../util/dropNull';
|
import { dropNull, shallowDropNull } from '../util/dropNull';
|
||||||
import { getOwn } from '../util/getOwn';
|
import { getOwn } from '../util/getOwn';
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
|
@ -262,7 +262,7 @@ export class CallingClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
private attemptToGiveOurUuidToRingRtc(): void {
|
private attemptToGiveOurUuidToRingRtc(): void {
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||||
if (!ourUuid) {
|
if (!ourUuid) {
|
||||||
// This can happen if we're not linked. It's okay if we hit this case.
|
// This can happen if we're not linked. It's okay if we hit this case.
|
||||||
return;
|
return;
|
||||||
|
@ -1392,15 +1392,17 @@ export class CallingClass {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const remoteUserId = envelope.sourceUuid || envelope.source;
|
const remoteUserId = envelope.sourceUuid;
|
||||||
const remoteDeviceId = this.parseDeviceId(envelope.sourceDevice);
|
const remoteDeviceId = this.parseDeviceId(envelope.sourceDevice);
|
||||||
if (!remoteUserId || !remoteDeviceId || !this.localDeviceId) {
|
if (!remoteUserId || !remoteDeviceId || !this.localDeviceId) {
|
||||||
window.log.error('Missing identifier, ignoring call message.');
|
window.log.error('Missing identifier, ignoring call message.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderIdentityRecord = window.textsecure.storage.protocol.getIdentityRecord(
|
const { storage } = window.textsecure;
|
||||||
remoteUserId
|
|
||||||
|
const senderIdentityRecord = await storage.protocol.getOrMigrateIdentityRecord(
|
||||||
|
new UUID(remoteUserId)
|
||||||
);
|
);
|
||||||
if (!senderIdentityRecord) {
|
if (!senderIdentityRecord) {
|
||||||
window.log.error(
|
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 senderIdentityKey = senderIdentityRecord.publicKey.slice(1); // Ignore the type header, it is not used.
|
||||||
|
|
||||||
const ourIdentifier =
|
const ourUuid = storage.user.getCheckedUuid();
|
||||||
window.textsecure.storage.user.getUuid() ||
|
|
||||||
window.textsecure.storage.user.getNumber();
|
|
||||||
assert(ourIdentifier, 'We should have either uuid or number');
|
|
||||||
|
|
||||||
const receiverIdentityRecord = window.textsecure.storage.protocol.getIdentityRecord(
|
const receiverIdentityRecord = storage.protocol.getIdentityRecord(ourUuid);
|
||||||
ourIdentifier
|
|
||||||
);
|
|
||||||
if (!receiverIdentityRecord) {
|
if (!receiverIdentityRecord) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'Missing receiver identity record; ignoring call message.'
|
'Missing receiver identity record; ignoring call message.'
|
||||||
|
|
|
@ -115,7 +115,7 @@ export function getCredentialsForToday(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function maybeFetchNewCredentials(): Promise<void> {
|
export async function maybeFetchNewCredentials(): Promise<void> {
|
||||||
const uuid = window.textsecure.storage.user.getUuid();
|
const uuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||||
if (!uuid) {
|
if (!uuid) {
|
||||||
window.log.info('maybeFetchCredentials: no UUID, returning early');
|
window.log.info('maybeFetchCredentials: no UUID, returning early');
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -166,6 +166,11 @@ async function generateManifest(
|
||||||
storageRecord.account = await toAccountRecord(conversation);
|
storageRecord.account = await toAccountRecord(conversation);
|
||||||
identifier.type = ITEM_TYPE.ACCOUNT;
|
identifier.type = ITEM_TYPE.ACCOUNT;
|
||||||
} else if (conversationType === ConversationTypes.Direct) {
|
} else if (conversationType === ConversationTypes.Direct) {
|
||||||
|
// Contacts must have UUID
|
||||||
|
if (!conversation.get('uuid')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
storageRecord = new Proto.StorageRecord();
|
storageRecord = new Proto.StorageRecord();
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
storageRecord.contact = await toContactRecord(conversation);
|
storageRecord.contact = await toContactRecord(conversation);
|
||||||
|
|
|
@ -35,6 +35,8 @@ import {
|
||||||
} from '../util/universalExpireTimer';
|
} from '../util/universalExpireTimer';
|
||||||
import { ourProfileKeyService } from './ourProfileKey';
|
import { ourProfileKeyService } from './ourProfileKey';
|
||||||
import { isGroupV1, isGroupV2 } from '../util/whatTypeOfConversation';
|
import { isGroupV1, isGroupV2 } from '../util/whatTypeOfConversation';
|
||||||
|
import { UUID } from '../types/UUID';
|
||||||
|
import * as Errors from '../types/errors';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
const { updateConversation } = dataInterface;
|
const { updateConversation } = dataInterface;
|
||||||
|
@ -118,9 +120,19 @@ export async function toContactRecord(
|
||||||
if (profileKey) {
|
if (profileKey) {
|
||||||
contactRecord.profileKey = Bytes.fromBase64(String(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) {
|
if (identityKey) {
|
||||||
contactRecord.identityKey = new FIXMEU8(identityKey);
|
contactRecord.identityKey = new FIXMEU8(identityKey);
|
||||||
}
|
}
|
||||||
|
@ -723,6 +735,11 @@ export async function mergeContactRecord(
|
||||||
const e164 = contactRecord.serviceE164 || undefined;
|
const e164 = contactRecord.serviceE164 || undefined;
|
||||||
const uuid = contactRecord.serviceUuid || undefined;
|
const uuid = contactRecord.serviceUuid || undefined;
|
||||||
|
|
||||||
|
// All contacts must have UUID
|
||||||
|
if (!uuid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const id = window.ConversationController.ensureContactIds({
|
const id = window.ConversationController.ensureContactIds({
|
||||||
e164,
|
e164,
|
||||||
uuid,
|
uuid,
|
||||||
|
|
|
@ -50,14 +50,17 @@ import {
|
||||||
ConversationType,
|
ConversationType,
|
||||||
DeleteSentProtoRecipientOptionsType,
|
DeleteSentProtoRecipientOptionsType,
|
||||||
IdentityKeyType,
|
IdentityKeyType,
|
||||||
|
IdentityKeyIdType,
|
||||||
ItemKeyType,
|
ItemKeyType,
|
||||||
ItemType,
|
ItemType,
|
||||||
LastConversationMessagesType,
|
LastConversationMessagesType,
|
||||||
MessageType,
|
MessageType,
|
||||||
MessageTypeUnhydrated,
|
MessageTypeUnhydrated,
|
||||||
PreKeyType,
|
PreKeyType,
|
||||||
|
PreKeyIdType,
|
||||||
SearchResultMessageType,
|
SearchResultMessageType,
|
||||||
SenderKeyType,
|
SenderKeyType,
|
||||||
|
SenderKeyIdType,
|
||||||
SentMessageDBType,
|
SentMessageDBType,
|
||||||
SentMessagesType,
|
SentMessagesType,
|
||||||
SentProtoType,
|
SentProtoType,
|
||||||
|
@ -66,7 +69,9 @@ import {
|
||||||
SentRecipientsType,
|
SentRecipientsType,
|
||||||
ServerInterface,
|
ServerInterface,
|
||||||
SessionType,
|
SessionType,
|
||||||
|
SessionIdType,
|
||||||
SignedPreKeyType,
|
SignedPreKeyType,
|
||||||
|
SignedPreKeyIdType,
|
||||||
StickerPackStatusType,
|
StickerPackStatusType,
|
||||||
StickerPackType,
|
StickerPackType,
|
||||||
StickerType,
|
StickerType,
|
||||||
|
@ -634,17 +639,10 @@ async function removeIndexedDBFiles() {
|
||||||
|
|
||||||
const IDENTITY_KEY_KEYS = ['publicKey'];
|
const IDENTITY_KEY_KEYS = ['publicKey'];
|
||||||
async function createOrUpdateIdentityKey(data: IdentityKeyType) {
|
async function createOrUpdateIdentityKey(data: IdentityKeyType) {
|
||||||
const updated = keysFromArrayBuffer(IDENTITY_KEY_KEYS, {
|
const updated = keysFromArrayBuffer(IDENTITY_KEY_KEYS, data);
|
||||||
...data,
|
|
||||||
id: window.ConversationController.getConversationId(data.id),
|
|
||||||
});
|
|
||||||
await channels.createOrUpdateIdentityKey(updated);
|
await channels.createOrUpdateIdentityKey(updated);
|
||||||
}
|
}
|
||||||
async function getIdentityKeyById(identifier: string) {
|
async function getIdentityKeyById(id: IdentityKeyIdType) {
|
||||||
const id = window.ConversationController.getConversationId(identifier);
|
|
||||||
if (!id) {
|
|
||||||
throw new Error('getIdentityKeyById: unable to find conversationId');
|
|
||||||
}
|
|
||||||
const data = await channels.getIdentityKeyById(id);
|
const data = await channels.getIdentityKeyById(id);
|
||||||
|
|
||||||
return keysToArrayBuffer(IDENTITY_KEY_KEYS, data);
|
return keysToArrayBuffer(IDENTITY_KEY_KEYS, data);
|
||||||
|
@ -655,11 +653,7 @@ async function bulkAddIdentityKeys(array: Array<IdentityKeyType>) {
|
||||||
);
|
);
|
||||||
await channels.bulkAddIdentityKeys(updated);
|
await channels.bulkAddIdentityKeys(updated);
|
||||||
}
|
}
|
||||||
async function removeIdentityKeyById(identifier: string) {
|
async function removeIdentityKeyById(id: IdentityKeyIdType) {
|
||||||
const id = window.ConversationController.getConversationId(identifier);
|
|
||||||
if (!id) {
|
|
||||||
throw new Error('removeIdentityKeyById: unable to find conversationId');
|
|
||||||
}
|
|
||||||
await channels.removeIdentityKeyById(id);
|
await channels.removeIdentityKeyById(id);
|
||||||
}
|
}
|
||||||
async function removeAllIdentityKeys() {
|
async function removeAllIdentityKeys() {
|
||||||
|
@ -677,7 +671,7 @@ async function createOrUpdatePreKey(data: PreKeyType) {
|
||||||
const updated = keysFromArrayBuffer(PRE_KEY_KEYS, data);
|
const updated = keysFromArrayBuffer(PRE_KEY_KEYS, data);
|
||||||
await channels.createOrUpdatePreKey(updated);
|
await channels.createOrUpdatePreKey(updated);
|
||||||
}
|
}
|
||||||
async function getPreKeyById(id: number) {
|
async function getPreKeyById(id: PreKeyIdType) {
|
||||||
const data = await channels.getPreKeyById(id);
|
const data = await channels.getPreKeyById(id);
|
||||||
|
|
||||||
return keysToArrayBuffer(PRE_KEY_KEYS, data);
|
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));
|
const updated = map(array, data => keysFromArrayBuffer(PRE_KEY_KEYS, data));
|
||||||
await channels.bulkAddPreKeys(updated);
|
await channels.bulkAddPreKeys(updated);
|
||||||
}
|
}
|
||||||
async function removePreKeyById(id: number) {
|
async function removePreKeyById(id: PreKeyIdType) {
|
||||||
await channels.removePreKeyById(id);
|
await channels.removePreKeyById(id);
|
||||||
}
|
}
|
||||||
async function removeAllPreKeys() {
|
async function removeAllPreKeys() {
|
||||||
|
@ -705,7 +699,7 @@ async function createOrUpdateSignedPreKey(data: SignedPreKeyType) {
|
||||||
const updated = keysFromArrayBuffer(PRE_KEY_KEYS, data);
|
const updated = keysFromArrayBuffer(PRE_KEY_KEYS, data);
|
||||||
await channels.createOrUpdateSignedPreKey(updated);
|
await channels.createOrUpdateSignedPreKey(updated);
|
||||||
}
|
}
|
||||||
async function getSignedPreKeyById(id: number) {
|
async function getSignedPreKeyById(id: SignedPreKeyIdType) {
|
||||||
const data = await channels.getSignedPreKeyById(id);
|
const data = await channels.getSignedPreKeyById(id);
|
||||||
|
|
||||||
return keysToArrayBuffer(PRE_KEY_KEYS, data);
|
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));
|
const updated = map(array, data => keysFromArrayBuffer(PRE_KEY_KEYS, data));
|
||||||
await channels.bulkAddSignedPreKeys(updated);
|
await channels.bulkAddSignedPreKeys(updated);
|
||||||
}
|
}
|
||||||
async function removeSignedPreKeyById(id: number) {
|
async function removeSignedPreKeyById(id: SignedPreKeyIdType) {
|
||||||
await channels.removeSignedPreKeyById(id);
|
await channels.removeSignedPreKeyById(id);
|
||||||
}
|
}
|
||||||
async function removeAllSignedPreKeys() {
|
async function removeAllSignedPreKeys() {
|
||||||
|
@ -731,7 +725,6 @@ async function removeAllSignedPreKeys() {
|
||||||
// Items
|
// Items
|
||||||
|
|
||||||
const ITEM_KEYS: Partial<Record<ItemKeyType, Array<string>>> = {
|
const ITEM_KEYS: Partial<Record<ItemKeyType, Array<string>>> = {
|
||||||
identityKey: ['value.pubKey', 'value.privKey'],
|
|
||||||
senderCertificate: ['value.serialized'],
|
senderCertificate: ['value.serialized'],
|
||||||
senderCertificateNoE164: ['value.serialized'],
|
senderCertificateNoE164: ['value.serialized'],
|
||||||
profileKey: ['value'],
|
profileKey: ['value'],
|
||||||
|
@ -749,7 +742,9 @@ async function createOrUpdateItem<K extends ItemKeyType>(data: ItemType<K>) {
|
||||||
|
|
||||||
await channels.createOrUpdateItem(updated);
|
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 keys = ITEM_KEYS[id];
|
||||||
const data = await channels.getItemById(id);
|
const data = await channels.getItemById(id);
|
||||||
|
|
||||||
|
@ -788,7 +783,7 @@ async function createOrUpdateSenderKey(key: SenderKeyType): Promise<void> {
|
||||||
await channels.createOrUpdateSenderKey(key);
|
await channels.createOrUpdateSenderKey(key);
|
||||||
}
|
}
|
||||||
async function getSenderKeyById(
|
async function getSenderKeyById(
|
||||||
id: string
|
id: SenderKeyIdType
|
||||||
): Promise<SenderKeyType | undefined> {
|
): Promise<SenderKeyType | undefined> {
|
||||||
return channels.getSenderKeyById(id);
|
return channels.getSenderKeyById(id);
|
||||||
}
|
}
|
||||||
|
@ -798,7 +793,7 @@ async function removeAllSenderKeys(): Promise<void> {
|
||||||
async function getAllSenderKeys(): Promise<Array<SenderKeyType>> {
|
async function getAllSenderKeys(): Promise<Array<SenderKeyType>> {
|
||||||
return channels.getAllSenderKeys();
|
return channels.getAllSenderKeys();
|
||||||
}
|
}
|
||||||
async function removeSenderKeyById(id: string): Promise<void> {
|
async function removeSenderKeyById(id: SenderKeyIdType): Promise<void> {
|
||||||
return channels.removeSenderKeyById(id);
|
return channels.removeSenderKeyById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -879,7 +874,7 @@ async function commitSessionsAndUnprocessed(options: {
|
||||||
async function bulkAddSessions(array: Array<SessionType>) {
|
async function bulkAddSessions(array: Array<SessionType>) {
|
||||||
await channels.bulkAddSessions(array);
|
await channels.bulkAddSessions(array);
|
||||||
}
|
}
|
||||||
async function removeSessionById(id: string) {
|
async function removeSessionById(id: SessionIdType) {
|
||||||
await channels.removeSessionById(id);
|
await channels.removeSessionById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ import type { ProcessGroupCallRingRequestResult } from '../types/Calling';
|
||||||
import { StorageAccessType } from '../types/Storage.d';
|
import { StorageAccessType } from '../types/Storage.d';
|
||||||
import type { AttachmentType } from '../types/Attachment';
|
import type { AttachmentType } from '../types/Attachment';
|
||||||
import { BodyRangesType } from '../types/Util';
|
import { BodyRangesType } from '../types/Util';
|
||||||
|
import type { QualifiedAddressStringType } from '../types/QualifiedAddress';
|
||||||
|
import type { UUIDStringType } from '../types/UUID';
|
||||||
import type { RemoveAllConfiguration } from '../types/RemoveAllConfiguration';
|
import type { RemoveAllConfiguration } from '../types/RemoveAllConfiguration';
|
||||||
|
|
||||||
export type AttachmentDownloadJobTypeType =
|
export type AttachmentDownloadJobTypeType =
|
||||||
|
@ -57,14 +59,17 @@ export type EmojiType = {
|
||||||
shortName: string;
|
shortName: string;
|
||||||
lastUsage: number;
|
lastUsage: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IdentityKeyType = {
|
export type IdentityKeyType = {
|
||||||
firstUse: boolean;
|
firstUse: boolean;
|
||||||
id: string;
|
id: UUIDStringType | `conversation:${UUIDStringType}`;
|
||||||
nonblockingApproval: boolean;
|
nonblockingApproval: boolean;
|
||||||
publicKey: ArrayBuffer;
|
publicKey: ArrayBuffer;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
verified: number;
|
verified: number;
|
||||||
};
|
};
|
||||||
|
export type IdentityKeyIdType = IdentityKeyType['id'];
|
||||||
|
|
||||||
export type ItemKeyType = keyof StorageAccessType;
|
export type ItemKeyType = keyof StorageAccessType;
|
||||||
export type AllItemsType = Partial<StorageAccessType>;
|
export type AllItemsType = Partial<StorageAccessType>;
|
||||||
export type ItemType<K extends ItemKeyType> = {
|
export type ItemType<K extends ItemKeyType> = {
|
||||||
|
@ -76,10 +81,13 @@ export type MessageTypeUnhydrated = {
|
||||||
json: string;
|
json: string;
|
||||||
};
|
};
|
||||||
export type PreKeyType = {
|
export type PreKeyType = {
|
||||||
id: number;
|
id: `${UUIDStringType}:${number}`;
|
||||||
|
keyId: number;
|
||||||
|
ourUuid: UUIDStringType;
|
||||||
privateKey: ArrayBuffer;
|
privateKey: ArrayBuffer;
|
||||||
publicKey: ArrayBuffer;
|
publicKey: ArrayBuffer;
|
||||||
};
|
};
|
||||||
|
export type PreKeyIdType = PreKeyType['id'];
|
||||||
export type SearchResultMessageType = {
|
export type SearchResultMessageType = {
|
||||||
json: string;
|
json: string;
|
||||||
snippet: string;
|
snippet: string;
|
||||||
|
@ -114,7 +122,7 @@ export type SentMessageDBType = {
|
||||||
|
|
||||||
export type SenderKeyType = {
|
export type SenderKeyType = {
|
||||||
// Primary key
|
// Primary key
|
||||||
id: string;
|
id: `${QualifiedAddressStringType}--${string}`;
|
||||||
// These two are combined into one string to give us the final id
|
// These two are combined into one string to give us the final id
|
||||||
senderId: string;
|
senderId: string;
|
||||||
distributionId: string;
|
distributionId: string;
|
||||||
|
@ -122,21 +130,28 @@ export type SenderKeyType = {
|
||||||
data: Buffer;
|
data: Buffer;
|
||||||
lastUpdatedDate: number;
|
lastUpdatedDate: number;
|
||||||
};
|
};
|
||||||
|
export type SenderKeyIdType = SenderKeyType['id'];
|
||||||
export type SessionType = {
|
export type SessionType = {
|
||||||
id: string;
|
id: QualifiedAddressStringType;
|
||||||
|
ourUuid: UUIDStringType;
|
||||||
|
uuid: UUIDStringType;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
deviceId: number;
|
deviceId: number;
|
||||||
record: string;
|
record: string;
|
||||||
version?: number;
|
version?: number;
|
||||||
};
|
};
|
||||||
|
export type SessionIdType = SessionType['id'];
|
||||||
export type SignedPreKeyType = {
|
export type SignedPreKeyType = {
|
||||||
confirmed: boolean;
|
confirmed: boolean;
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
created_at: number;
|
created_at: number;
|
||||||
id: number;
|
ourUuid: UUIDStringType;
|
||||||
|
id: `${UUIDStringType}:${number}`;
|
||||||
|
keyId: number;
|
||||||
privateKey: ArrayBuffer;
|
privateKey: ArrayBuffer;
|
||||||
publicKey: ArrayBuffer;
|
publicKey: ArrayBuffer;
|
||||||
};
|
};
|
||||||
|
export type SignedPreKeyIdType = SignedPreKeyType['id'];
|
||||||
|
|
||||||
export type StickerType = Readonly<{
|
export type StickerType = Readonly<{
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -227,23 +242,27 @@ export type DataInterface = {
|
||||||
removeIndexedDBFiles: () => Promise<void>;
|
removeIndexedDBFiles: () => Promise<void>;
|
||||||
|
|
||||||
createOrUpdateIdentityKey: (data: IdentityKeyType) => 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>;
|
bulkAddIdentityKeys: (array: Array<IdentityKeyType>) => Promise<void>;
|
||||||
removeIdentityKeyById: (id: string) => Promise<void>;
|
removeIdentityKeyById: (id: IdentityKeyIdType) => Promise<void>;
|
||||||
removeAllIdentityKeys: () => Promise<void>;
|
removeAllIdentityKeys: () => Promise<void>;
|
||||||
getAllIdentityKeys: () => Promise<Array<IdentityKeyType>>;
|
getAllIdentityKeys: () => Promise<Array<IdentityKeyType>>;
|
||||||
|
|
||||||
createOrUpdatePreKey: (data: PreKeyType) => Promise<void>;
|
createOrUpdatePreKey: (data: PreKeyType) => Promise<void>;
|
||||||
getPreKeyById: (id: number) => Promise<PreKeyType | undefined>;
|
getPreKeyById: (id: PreKeyIdType) => Promise<PreKeyType | undefined>;
|
||||||
bulkAddPreKeys: (array: Array<PreKeyType>) => Promise<void>;
|
bulkAddPreKeys: (array: Array<PreKeyType>) => Promise<void>;
|
||||||
removePreKeyById: (id: number) => Promise<void>;
|
removePreKeyById: (id: PreKeyIdType) => Promise<void>;
|
||||||
removeAllPreKeys: () => Promise<void>;
|
removeAllPreKeys: () => Promise<void>;
|
||||||
getAllPreKeys: () => Promise<Array<PreKeyType>>;
|
getAllPreKeys: () => Promise<Array<PreKeyType>>;
|
||||||
|
|
||||||
createOrUpdateSignedPreKey: (data: SignedPreKeyType) => Promise<void>;
|
createOrUpdateSignedPreKey: (data: SignedPreKeyType) => Promise<void>;
|
||||||
getSignedPreKeyById: (id: number) => Promise<SignedPreKeyType | undefined>;
|
getSignedPreKeyById: (
|
||||||
|
id: SignedPreKeyIdType
|
||||||
|
) => Promise<SignedPreKeyType | undefined>;
|
||||||
bulkAddSignedPreKeys: (array: Array<SignedPreKeyType>) => Promise<void>;
|
bulkAddSignedPreKeys: (array: Array<SignedPreKeyType>) => Promise<void>;
|
||||||
removeSignedPreKeyById: (id: number) => Promise<void>;
|
removeSignedPreKeyById: (id: SignedPreKeyIdType) => Promise<void>;
|
||||||
removeAllSignedPreKeys: () => Promise<void>;
|
removeAllSignedPreKeys: () => Promise<void>;
|
||||||
getAllSignedPreKeys: () => Promise<Array<SignedPreKeyType>>;
|
getAllSignedPreKeys: () => Promise<Array<SignedPreKeyType>>;
|
||||||
|
|
||||||
|
@ -254,10 +273,10 @@ export type DataInterface = {
|
||||||
getAllItems: () => Promise<AllItemsType>;
|
getAllItems: () => Promise<AllItemsType>;
|
||||||
|
|
||||||
createOrUpdateSenderKey: (key: SenderKeyType) => Promise<void>;
|
createOrUpdateSenderKey: (key: SenderKeyType) => Promise<void>;
|
||||||
getSenderKeyById: (id: string) => Promise<SenderKeyType | undefined>;
|
getSenderKeyById: (id: SenderKeyIdType) => Promise<SenderKeyType | undefined>;
|
||||||
removeAllSenderKeys: () => Promise<void>;
|
removeAllSenderKeys: () => Promise<void>;
|
||||||
getAllSenderKeys: () => Promise<Array<SenderKeyType>>;
|
getAllSenderKeys: () => Promise<Array<SenderKeyType>>;
|
||||||
removeSenderKeyById: (id: string) => Promise<void>;
|
removeSenderKeyById: (id: SenderKeyIdType) => Promise<void>;
|
||||||
|
|
||||||
insertSentProto: (
|
insertSentProto: (
|
||||||
proto: SentProtoType,
|
proto: SentProtoType,
|
||||||
|
@ -296,7 +315,7 @@ export type DataInterface = {
|
||||||
unprocessed: Array<UnprocessedType>;
|
unprocessed: Array<UnprocessedType>;
|
||||||
}): Promise<void>;
|
}): Promise<void>;
|
||||||
bulkAddSessions: (array: Array<SessionType>) => Promise<void>;
|
bulkAddSessions: (array: Array<SessionType>) => Promise<void>;
|
||||||
removeSessionById: (id: string) => Promise<void>;
|
removeSessionById: (id: SessionIdType) => Promise<void>;
|
||||||
removeSessionsByConversation: (conversationId: string) => Promise<void>;
|
removeSessionsByConversation: (conversationId: string) => Promise<void>;
|
||||||
removeAllSessions: () => Promise<void>;
|
removeAllSessions: () => Promise<void>;
|
||||||
getAllSessions: () => Promise<Array<SessionType>>;
|
getAllSessions: () => Promise<Array<SessionType>>;
|
||||||
|
|
416
ts/sql/Server.ts
416
ts/sql/Server.ts
|
@ -30,6 +30,7 @@ import {
|
||||||
} from 'lodash';
|
} from 'lodash';
|
||||||
|
|
||||||
import { ReadStatus } from '../messages/MessageReadStatus';
|
import { ReadStatus } from '../messages/MessageReadStatus';
|
||||||
|
import Helpers from '../textsecure/Helpers';
|
||||||
import { GroupV2MemberType } from '../model-types.d';
|
import { GroupV2MemberType } from '../model-types.d';
|
||||||
import { ReactionType } from '../types/Reactions';
|
import { ReactionType } from '../types/Reactions';
|
||||||
import { STORAGE_UI_KEYS } from '../types/StorageUIKeys';
|
import { STORAGE_UI_KEYS } from '../types/StorageUIKeys';
|
||||||
|
@ -40,6 +41,7 @@ import { dropNull } from '../util/dropNull';
|
||||||
import { isNormalNumber } from '../util/isNormalNumber';
|
import { isNormalNumber } from '../util/isNormalNumber';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
import { isValidGuid } from '../util/isValidGuid';
|
||||||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
import { formatCountForLogging } from '../logging/formatCountForLogging';
|
import { formatCountForLogging } from '../logging/formatCountForLogging';
|
||||||
|
@ -55,6 +57,7 @@ import {
|
||||||
DeleteSentProtoRecipientOptionsType,
|
DeleteSentProtoRecipientOptionsType,
|
||||||
EmojiType,
|
EmojiType,
|
||||||
IdentityKeyType,
|
IdentityKeyType,
|
||||||
|
IdentityKeyIdType,
|
||||||
ItemKeyType,
|
ItemKeyType,
|
||||||
ItemType,
|
ItemType,
|
||||||
LastConversationMessagesServerType,
|
LastConversationMessagesServerType,
|
||||||
|
@ -62,8 +65,10 @@ import {
|
||||||
MessageType,
|
MessageType,
|
||||||
MessageTypeUnhydrated,
|
MessageTypeUnhydrated,
|
||||||
PreKeyType,
|
PreKeyType,
|
||||||
|
PreKeyIdType,
|
||||||
SearchResultMessageType,
|
SearchResultMessageType,
|
||||||
SenderKeyType,
|
SenderKeyType,
|
||||||
|
SenderKeyIdType,
|
||||||
SentMessageDBType,
|
SentMessageDBType,
|
||||||
SentMessagesType,
|
SentMessagesType,
|
||||||
SentProtoType,
|
SentProtoType,
|
||||||
|
@ -72,7 +77,9 @@ import {
|
||||||
SentRecipientsType,
|
SentRecipientsType,
|
||||||
ServerInterface,
|
ServerInterface,
|
||||||
SessionType,
|
SessionType,
|
||||||
|
SessionIdType,
|
||||||
SignedPreKeyType,
|
SignedPreKeyType,
|
||||||
|
SignedPreKeyIdType,
|
||||||
StickerPackStatusType,
|
StickerPackStatusType,
|
||||||
StickerPackType,
|
StickerPackType,
|
||||||
StickerType,
|
StickerType,
|
||||||
|
@ -2101,6 +2108,336 @@ function updateToSchemaVersion40(currentVersion: number, db: Database) {
|
||||||
console.log('updateToSchemaVersion40: success!');
|
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 = [
|
const SCHEMA_VERSIONS = [
|
||||||
updateToSchemaVersion1,
|
updateToSchemaVersion1,
|
||||||
updateToSchemaVersion2,
|
updateToSchemaVersion2,
|
||||||
|
@ -2142,6 +2479,7 @@ const SCHEMA_VERSIONS = [
|
||||||
updateToSchemaVersion38,
|
updateToSchemaVersion38,
|
||||||
updateToSchemaVersion39,
|
updateToSchemaVersion39,
|
||||||
updateToSchemaVersion40,
|
updateToSchemaVersion40,
|
||||||
|
updateToSchemaVersion41,
|
||||||
];
|
];
|
||||||
|
|
||||||
function updateSchema(db: Database): void {
|
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 globalInstance: Database | undefined;
|
||||||
let globalInstanceRenderer: Database | undefined;
|
let globalInstanceRenderer: Database | undefined;
|
||||||
let databaseFilePath: string | undefined;
|
let databaseFilePath: string | undefined;
|
||||||
|
@ -2370,13 +2725,15 @@ const IDENTITY_KEYS_TABLE = 'identityKeys';
|
||||||
function createOrUpdateIdentityKey(data: IdentityKeyType): Promise<void> {
|
function createOrUpdateIdentityKey(data: IdentityKeyType): Promise<void> {
|
||||||
return createOrUpdate(IDENTITY_KEYS_TABLE, data);
|
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);
|
return getById(IDENTITY_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
function bulkAddIdentityKeys(array: Array<IdentityKeyType>): Promise<void> {
|
function bulkAddIdentityKeys(array: Array<IdentityKeyType>): Promise<void> {
|
||||||
return bulkAdd(IDENTITY_KEYS_TABLE, array);
|
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);
|
return removeById(IDENTITY_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
function removeAllIdentityKeys(): Promise<void> {
|
function removeAllIdentityKeys(): Promise<void> {
|
||||||
|
@ -2390,13 +2747,15 @@ const PRE_KEYS_TABLE = 'preKeys';
|
||||||
function createOrUpdatePreKey(data: PreKeyType): Promise<void> {
|
function createOrUpdatePreKey(data: PreKeyType): Promise<void> {
|
||||||
return createOrUpdate(PRE_KEYS_TABLE, data);
|
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);
|
return getById(PRE_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
function bulkAddPreKeys(array: Array<PreKeyType>): Promise<void> {
|
function bulkAddPreKeys(array: Array<PreKeyType>): Promise<void> {
|
||||||
return bulkAdd(PRE_KEYS_TABLE, array);
|
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);
|
return removeById(PRE_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
function removeAllPreKeys(): Promise<void> {
|
function removeAllPreKeys(): Promise<void> {
|
||||||
|
@ -2410,15 +2769,15 @@ const SIGNED_PRE_KEYS_TABLE = 'signedPreKeys';
|
||||||
function createOrUpdateSignedPreKey(data: SignedPreKeyType): Promise<void> {
|
function createOrUpdateSignedPreKey(data: SignedPreKeyType): Promise<void> {
|
||||||
return createOrUpdate(SIGNED_PRE_KEYS_TABLE, data);
|
return createOrUpdate(SIGNED_PRE_KEYS_TABLE, data);
|
||||||
}
|
}
|
||||||
function getSignedPreKeyById(
|
async function getSignedPreKeyById(
|
||||||
id: number
|
id: SignedPreKeyIdType
|
||||||
): Promise<SignedPreKeyType | undefined> {
|
): Promise<SignedPreKeyType | undefined> {
|
||||||
return getById(SIGNED_PRE_KEYS_TABLE, id);
|
return getById(SIGNED_PRE_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
function bulkAddSignedPreKeys(array: Array<SignedPreKeyType>): Promise<void> {
|
function bulkAddSignedPreKeys(array: Array<SignedPreKeyType>): Promise<void> {
|
||||||
return bulkAdd(SIGNED_PRE_KEYS_TABLE, array);
|
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);
|
return removeById(SIGNED_PRE_KEYS_TABLE, id);
|
||||||
}
|
}
|
||||||
function removeAllSignedPreKeys(): Promise<void> {
|
function removeAllSignedPreKeys(): Promise<void> {
|
||||||
|
@ -2445,7 +2804,7 @@ function createOrUpdateItem<K extends ItemKeyType>(
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return createOrUpdate(ITEMS_TABLE, data);
|
return createOrUpdate(ITEMS_TABLE, data);
|
||||||
}
|
}
|
||||||
function getItemById<K extends ItemKeyType>(
|
async function getItemById<K extends ItemKeyType>(
|
||||||
id: K
|
id: K
|
||||||
): Promise<ItemType<K> | undefined> {
|
): Promise<ItemType<K> | undefined> {
|
||||||
return getById(ITEMS_TABLE, id);
|
return getById(ITEMS_TABLE, id);
|
||||||
|
@ -2467,7 +2826,7 @@ async function getAllItems(): Promise<AllItemsType> {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
function removeItemById(id: ItemKeyType): Promise<void> {
|
async function removeItemById(id: ItemKeyType): Promise<void> {
|
||||||
return removeById(ITEMS_TABLE, id);
|
return removeById(ITEMS_TABLE, id);
|
||||||
}
|
}
|
||||||
function removeAllItems(): Promise<void> {
|
function removeAllItems(): Promise<void> {
|
||||||
|
@ -2497,7 +2856,7 @@ async function createOrUpdateSenderKey(key: SenderKeyType): Promise<void> {
|
||||||
).run(key);
|
).run(key);
|
||||||
}
|
}
|
||||||
async function getSenderKeyById(
|
async function getSenderKeyById(
|
||||||
id: string
|
id: SenderKeyIdType
|
||||||
): Promise<SenderKeyType | undefined> {
|
): Promise<SenderKeyType | undefined> {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
const row = prepare(db, 'SELECT * FROM senderKeys WHERE id = $id').get({
|
const row = prepare(db, 'SELECT * FROM senderKeys WHERE id = $id').get({
|
||||||
|
@ -2516,7 +2875,7 @@ async function getAllSenderKeys(): Promise<Array<SenderKeyType>> {
|
||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
async function removeSenderKeyById(id: string): Promise<void> {
|
async function removeSenderKeyById(id: SenderKeyIdType): Promise<void> {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
prepare(db, 'DELETE FROM senderKeys WHERE id = $id').run({ id });
|
prepare(db, 'DELETE FROM senderKeys WHERE id = $id').run({ id });
|
||||||
}
|
}
|
||||||
|
@ -2840,7 +3199,7 @@ async function _getAllSentProtoMessageIds(): Promise<Array<SentMessageDBType>> {
|
||||||
const SESSIONS_TABLE = 'sessions';
|
const SESSIONS_TABLE = 'sessions';
|
||||||
function createOrUpdateSessionSync(data: SessionType): void {
|
function createOrUpdateSessionSync(data: SessionType): void {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
const { id, conversationId } = data;
|
const { id, conversationId, ourUuid, uuid } = data;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'createOrUpdateSession: Provided data did not have a truthy id'
|
'createOrUpdateSession: Provided data did not have a truthy id'
|
||||||
|
@ -2858,16 +3217,22 @@ function createOrUpdateSessionSync(data: SessionType): void {
|
||||||
INSERT OR REPLACE INTO sessions (
|
INSERT OR REPLACE INTO sessions (
|
||||||
id,
|
id,
|
||||||
conversationId,
|
conversationId,
|
||||||
|
ourUuid,
|
||||||
|
uuid,
|
||||||
json
|
json
|
||||||
) values (
|
) values (
|
||||||
$id,
|
$id,
|
||||||
$conversationId,
|
$conversationId,
|
||||||
|
$ourUuid,
|
||||||
|
$uuid,
|
||||||
$json
|
$json
|
||||||
)
|
)
|
||||||
`
|
`
|
||||||
).run({
|
).run({
|
||||||
id,
|
id,
|
||||||
conversationId,
|
conversationId,
|
||||||
|
ourUuid,
|
||||||
|
uuid,
|
||||||
json: objectToJSON(data),
|
json: objectToJSON(data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2910,7 +3275,7 @@ async function commitSessionsAndUnprocessed({
|
||||||
function bulkAddSessions(array: Array<SessionType>): Promise<void> {
|
function bulkAddSessions(array: Array<SessionType>): Promise<void> {
|
||||||
return bulkAdd(SESSIONS_TABLE, array);
|
return bulkAdd(SESSIONS_TABLE, array);
|
||||||
}
|
}
|
||||||
function removeSessionById(id: string): Promise<void> {
|
async function removeSessionById(id: SessionIdType): Promise<void> {
|
||||||
return removeById(SESSIONS_TABLE, id);
|
return removeById(SESSIONS_TABLE, id);
|
||||||
}
|
}
|
||||||
async function removeSessionsByConversation(
|
async function removeSessionsByConversation(
|
||||||
|
@ -2933,11 +3298,11 @@ function getAllSessions(): Promise<Array<SessionType>> {
|
||||||
return getAllFromTable(SESSIONS_TABLE);
|
return getAllFromTable(SESSIONS_TABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOrUpdateSync(
|
function createOrUpdateSync<Key extends string | number>(
|
||||||
table: string,
|
table: string,
|
||||||
data: Record<string, unknown> & { id: string | number }
|
data: Record<string, unknown> & { id: Key },
|
||||||
|
db = getInstance()
|
||||||
): void {
|
): void {
|
||||||
const db = getInstance();
|
|
||||||
const { id } = data;
|
const { id } = data;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new Error('createOrUpdate: Provided data did not have a truthy 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,
|
table: string,
|
||||||
id: string | number
|
id: Key,
|
||||||
): Promise<T | undefined> {
|
db = getInstance()
|
||||||
const db = getInstance();
|
): Result | undefined {
|
||||||
const row = db
|
const row = db
|
||||||
.prepare<Query>(
|
.prepare<Query>(
|
||||||
`
|
`
|
||||||
|
@ -3003,12 +3368,11 @@ async function getById<T>(
|
||||||
return jsonToObject(row.json);
|
return jsonToObject(row.json);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeById(
|
function removeById<Key extends string | number>(
|
||||||
table: string,
|
table: string,
|
||||||
id: string | number | Array<string | number>
|
id: Key | Array<Key>,
|
||||||
): Promise<void> {
|
db = getInstance()
|
||||||
const db = getInstance();
|
): void {
|
||||||
|
|
||||||
if (!Array.isArray(id)) {
|
if (!Array.isArray(id)) {
|
||||||
db.prepare<Query>(
|
db.prepare<Query>(
|
||||||
`
|
`
|
||||||
|
@ -4922,7 +5286,7 @@ async function resetAttachmentDownloadPending(): Promise<void> {
|
||||||
`
|
`
|
||||||
).run();
|
).run();
|
||||||
}
|
}
|
||||||
function removeAttachmentDownloadJob(id: string): Promise<void> {
|
async function removeAttachmentDownloadJob(id: string): Promise<void> {
|
||||||
return removeById(ATTACHMENT_DOWNLOADS_TABLE, id);
|
return removeById(ATTACHMENT_DOWNLOADS_TABLE, id);
|
||||||
}
|
}
|
||||||
function removeAllAttachmentDownloadJobs(): Promise<void> {
|
function removeAllAttachmentDownloadJobs(): Promise<void> {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
import { v4 as getGuid } from 'uuid';
|
||||||
|
|
||||||
import MessageReceiver from '../textsecure/MessageReceiver';
|
import MessageReceiver from '../textsecure/MessageReceiver';
|
||||||
import { IncomingWebSocketRequest } from '../textsecure/WebsocketResources';
|
import { IncomingWebSocketRequest } from '../textsecure/WebsocketResources';
|
||||||
|
@ -22,11 +23,20 @@ describe('MessageReceiver', () => {
|
||||||
const uuid = 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee';
|
const uuid = 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee';
|
||||||
const deviceId = 1;
|
const deviceId = 1;
|
||||||
|
|
||||||
|
let oldUuid: string | undefined;
|
||||||
|
let oldDeviceId: number | undefined;
|
||||||
|
|
||||||
beforeEach(async () => {
|
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();
|
await window.storage.protocol.hydrateCaches();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
if (oldUuid !== undefined && oldDeviceId !== undefined) {
|
||||||
|
await window.storage.user.setUuidAndDeviceId(oldUuid, oldDeviceId);
|
||||||
|
}
|
||||||
await window.storage.protocol.removeAllUnprocessed();
|
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
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
import { v4 as getGuid } from 'uuid';
|
||||||
|
|
||||||
import { getRandomBytes } from '../../Crypto';
|
import { getRandomBytes } from '../../Crypto';
|
||||||
import AccountManager from '../../textsecure/AccountManager';
|
import AccountManager from '../../textsecure/AccountManager';
|
||||||
import { OuterSignedPrekeyType } from '../../textsecure/Types.d';
|
import { OuterSignedPrekeyType } from '../../textsecure/Types.d';
|
||||||
|
import { UUID } from '../../types/UUID';
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
@ -21,15 +23,18 @@ describe('AccountManager', () => {
|
||||||
let originalGetIdentityKeyPair: any;
|
let originalGetIdentityKeyPair: any;
|
||||||
let originalLoadSignedPreKeys: any;
|
let originalLoadSignedPreKeys: any;
|
||||||
let originalRemoveSignedPreKey: any;
|
let originalRemoveSignedPreKey: any;
|
||||||
|
let originalGetUuid: any;
|
||||||
let signedPreKeys: Array<OuterSignedPrekeyType>;
|
let signedPreKeys: Array<OuterSignedPrekeyType>;
|
||||||
const DAY = 1000 * 60 * 60 * 24;
|
const DAY = 1000 * 60 * 60 * 24;
|
||||||
|
|
||||||
const pubKey = getRandomBytes(33);
|
const pubKey = getRandomBytes(33);
|
||||||
const privKey = getRandomBytes(32);
|
const privKey = getRandomBytes(32);
|
||||||
|
const identityKey = window.Signal.Curve.generateKeyPair();
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const identityKey = window.Signal.Curve.generateKeyPair();
|
const ourUuid = new UUID(getGuid());
|
||||||
|
|
||||||
|
originalGetUuid = window.textsecure.storage.user.getUuid;
|
||||||
originalGetIdentityKeyPair =
|
originalGetIdentityKeyPair =
|
||||||
window.textsecure.storage.protocol.getIdentityKeyPair;
|
window.textsecure.storage.protocol.getIdentityKeyPair;
|
||||||
originalLoadSignedPreKeys =
|
originalLoadSignedPreKeys =
|
||||||
|
@ -37,12 +42,15 @@ describe('AccountManager', () => {
|
||||||
originalRemoveSignedPreKey =
|
originalRemoveSignedPreKey =
|
||||||
window.textsecure.storage.protocol.removeSignedPreKey;
|
window.textsecure.storage.protocol.removeSignedPreKey;
|
||||||
|
|
||||||
|
window.textsecure.storage.user.getUuid = () => ourUuid;
|
||||||
|
|
||||||
window.textsecure.storage.protocol.getIdentityKeyPair = async () =>
|
window.textsecure.storage.protocol.getIdentityKeyPair = async () =>
|
||||||
identityKey;
|
identityKey;
|
||||||
window.textsecure.storage.protocol.loadSignedPreKeys = async () =>
|
window.textsecure.storage.protocol.loadSignedPreKeys = async () =>
|
||||||
signedPreKeys;
|
signedPreKeys;
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
window.textsecure.storage.user.getUuid = originalGetUuid;
|
||||||
window.textsecure.storage.protocol.getIdentityKeyPair = originalGetIdentityKeyPair;
|
window.textsecure.storage.protocol.getIdentityKeyPair = originalGetIdentityKeyPair;
|
||||||
window.textsecure.storage.protocol.loadSignedPreKeys = originalLoadSignedPreKeys;
|
window.textsecure.storage.protocol.loadSignedPreKeys = originalLoadSignedPreKeys;
|
||||||
window.textsecure.storage.protocol.removeSignedPreKey = originalRemoveSignedPreKey;
|
window.textsecure.storage.protocol.removeSignedPreKey = originalRemoveSignedPreKey;
|
||||||
|
@ -51,7 +59,10 @@ describe('AccountManager', () => {
|
||||||
describe('encrypted device name', () => {
|
describe('encrypted device name', () => {
|
||||||
it('roundtrips', async () => {
|
it('roundtrips', async () => {
|
||||||
const deviceName = 'v2.5.0 on Ubunto 20.04';
|
const deviceName = 'v2.5.0 on Ubunto 20.04';
|
||||||
const encrypted = await accountManager.encryptDeviceName(deviceName);
|
const encrypted = await accountManager.encryptDeviceName(
|
||||||
|
deviceName,
|
||||||
|
identityKey
|
||||||
|
);
|
||||||
if (!encrypted) {
|
if (!encrypted) {
|
||||||
throw new Error('failed to encrypt!');
|
throw new Error('failed to encrypt!');
|
||||||
}
|
}
|
||||||
|
@ -62,7 +73,10 @@ describe('AccountManager', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles falsey deviceName', async () => {
|
it('handles falsey deviceName', async () => {
|
||||||
const encrypted = await accountManager.encryptDeviceName('');
|
const encrypted = await accountManager.encryptDeviceName(
|
||||||
|
'',
|
||||||
|
identityKey
|
||||||
|
);
|
||||||
assert.strictEqual(encrypted, null);
|
assert.strictEqual(encrypted, null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -146,7 +160,10 @@ describe('AccountManager', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
window.textsecure.storage.protocol.removeSignedPreKey = async keyId => {
|
window.textsecure.storage.protocol.removeSignedPreKey = async (
|
||||||
|
_,
|
||||||
|
keyId
|
||||||
|
) => {
|
||||||
if (keyId !== 4) {
|
if (keyId !== 4) {
|
||||||
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
throw new Error(`Wrong keys were eliminated! ${keyId}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,56 @@
|
||||||
// Copyright 2017-2021 Signal Messenger, LLC
|
// Copyright 2017-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// 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', () => {
|
describe('KeyChangeListener', () => {
|
||||||
const STORAGE_KEYS_TO_RESTORE = ['number_id', 'uuid_id'];
|
let oldNumberId: string | undefined;
|
||||||
const oldStorageValues = new Map();
|
let oldUuidId: string | undefined;
|
||||||
|
|
||||||
const phoneNumberWithKeyChange = '+13016886524'; // nsa
|
const ourUuid = getGuid();
|
||||||
const addressString = `${phoneNumberWithKeyChange}.1`;
|
const uuidWithKeyChange = getGuid();
|
||||||
const oldKey = window.Signal.Crypto.getRandomBytes(33);
|
const address = Address.create(uuidWithKeyChange, 1);
|
||||||
const newKey = window.Signal.Crypto.getRandomBytes(33);
|
const oldKey = getRandomBytes(33);
|
||||||
let store;
|
const newKey = getRandomBytes(33);
|
||||||
|
let store: SignalProtocolStore;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
window.ConversationController.reset();
|
window.ConversationController.reset();
|
||||||
await window.ConversationController.load();
|
await window.ConversationController.load();
|
||||||
|
|
||||||
STORAGE_KEYS_TO_RESTORE.forEach(key => {
|
const { storage } = window.textsecure;
|
||||||
oldStorageValues.set(key, window.textsecure.storage.get(key));
|
|
||||||
});
|
oldNumberId = storage.get('number_id');
|
||||||
window.textsecure.storage.put('number_id', '+14155555556.2');
|
oldUuidId = storage.get('uuid_id');
|
||||||
window.textsecure.storage.put('uuid_id', `${window.getGuid()}.2`);
|
await storage.put('number_id', '+14155555556.2');
|
||||||
|
await storage.put('uuid_id', `${ourUuid}.2`);
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
await window.Signal.Data.removeAll();
|
await window.Signal.Data.removeAll();
|
||||||
await window.storage.fetch();
|
|
||||||
|
|
||||||
oldStorageValues.forEach((oldValue, key) => {
|
const { storage } = window.textsecure;
|
||||||
if (oldValue) {
|
await storage.fetch();
|
||||||
window.textsecure.storage.put(key, oldValue);
|
|
||||||
} else {
|
if (oldNumberId) {
|
||||||
window.textsecure.storage.remove(key);
|
await storage.put('number_id', oldNumberId);
|
||||||
}
|
}
|
||||||
});
|
if (oldUuidId) {
|
||||||
|
await storage.put('uuid_id', oldUuidId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let convo;
|
let convo: ConversationModel;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
window.ConversationController.reset();
|
window.ConversationController.reset();
|
||||||
|
@ -45,46 +58,46 @@ describe('KeyChangeListener', () => {
|
||||||
await window.ConversationController.loadPromise();
|
await window.ConversationController.loadPromise();
|
||||||
|
|
||||||
convo = window.ConversationController.dangerouslyCreateAndAdd({
|
convo = window.ConversationController.dangerouslyCreateAndAdd({
|
||||||
id: phoneNumberWithKeyChange,
|
id: uuidWithKeyChange,
|
||||||
type: 'private',
|
type: 'private',
|
||||||
});
|
});
|
||||||
await window.Signal.Data.saveConversation(convo.attributes);
|
await window.Signal.Data.saveConversation(convo.attributes);
|
||||||
|
|
||||||
store = new SignalProtocolStore();
|
store = new SignalProtocolStore();
|
||||||
await store.hydrateCaches();
|
await store.hydrateCaches();
|
||||||
Whisper.KeyChangeListener.init(store);
|
KeyChangeListener.init(store);
|
||||||
return store.saveIdentity(addressString, oldKey);
|
return store.saveIdentity(address, oldKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await window.Signal.Data.removeAllMessagesInConversation(convo.id, {
|
await window.Signal.Data.removeAllMessagesInConversation(convo.id, {
|
||||||
logId: phoneNumberWithKeyChange,
|
logId: uuidWithKeyChange,
|
||||||
MessageCollection: Whisper.MessageCollection,
|
MessageCollection: Whisper.MessageCollection,
|
||||||
});
|
});
|
||||||
await window.Signal.Data.removeConversation(convo.id, {
|
await window.Signal.Data.removeConversation(convo.id, {
|
||||||
Conversation: Whisper.Conversation,
|
Conversation: Whisper.Conversation,
|
||||||
});
|
});
|
||||||
|
|
||||||
await store.removeIdentityKey(phoneNumberWithKeyChange);
|
await store.removeIdentityKey(new UUID(uuidWithKeyChange));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When we have a conversation with this contact', () => {
|
describe('When we have a conversation with this contact', () => {
|
||||||
it('generates a key change notice in the private conversation with this contact', done => {
|
it('generates a key change notice in the private conversation with this contact', done => {
|
||||||
const original = convo.addKeyChange;
|
const original = convo.addKeyChange;
|
||||||
convo.addKeyChange = keyChangedId => {
|
convo.addKeyChange = async keyChangedId => {
|
||||||
assert.equal(phoneNumberWithKeyChange, keyChangedId);
|
assert.equal(uuidWithKeyChange, keyChangedId.toString());
|
||||||
convo.addKeyChange = original;
|
convo.addKeyChange = original;
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
store.saveIdentity(addressString, newKey);
|
store.saveIdentity(address, newKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When we have a group with this contact', () => {
|
describe('When we have a group with this contact', () => {
|
||||||
let groupConvo;
|
let groupConvo: ConversationModel;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
groupConvo = ConversationController.dangerouslyCreateAndAdd({
|
groupConvo = window.ConversationController.dangerouslyCreateAndAdd({
|
||||||
id: 'groupId',
|
id: 'groupId',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
members: [convo.id],
|
members: [convo.id],
|
||||||
|
@ -94,7 +107,7 @@ describe('KeyChangeListener', () => {
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await window.Signal.Data.removeAllMessagesInConversation(groupConvo.id, {
|
await window.Signal.Data.removeAllMessagesInConversation(groupConvo.id, {
|
||||||
logId: phoneNumberWithKeyChange,
|
logId: uuidWithKeyChange,
|
||||||
MessageCollection: Whisper.MessageCollection,
|
MessageCollection: Whisper.MessageCollection,
|
||||||
});
|
});
|
||||||
await window.Signal.Data.removeConversation(groupConvo.id, {
|
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 => {
|
it('generates a key change notice in the group conversation with this contact', done => {
|
||||||
const original = groupConvo.addKeyChange;
|
const original = groupConvo.addKeyChange;
|
||||||
groupConvo.addKeyChange = keyChangedId => {
|
groupConvo.addKeyChange = async keyChangedId => {
|
||||||
assert.equal(phoneNumberWithKeyChange, keyChangedId);
|
assert.equal(uuidWithKeyChange, keyChangedId.toString());
|
||||||
groupConvo.addKeyChange = original;
|
groupConvo.addKeyChange = original;
|
||||||
done();
|
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 class-methods-use-this */
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
import EventTarget from './EventTarget';
|
import EventTarget from './EventTarget';
|
||||||
import { WebAPIType } from './WebAPI';
|
import { WebAPIType } from './WebAPI';
|
||||||
|
@ -21,6 +22,7 @@ import {
|
||||||
generateRegistrationId,
|
generateRegistrationId,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
typedArrayToArrayBuffer,
|
typedArrayToArrayBuffer,
|
||||||
|
arrayBufferToBase64,
|
||||||
} from '../Crypto';
|
} from '../Crypto';
|
||||||
import {
|
import {
|
||||||
generateKeyPair,
|
generateKeyPair,
|
||||||
|
@ -29,7 +31,7 @@ import {
|
||||||
} from '../Curve';
|
} from '../Curve';
|
||||||
import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
|
import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
|
||||||
import { ourProfileKeyService } from '../services/ourProfileKey';
|
import { ourProfileKeyService } from '../services/ourProfileKey';
|
||||||
import { assert } from '../util/assert';
|
import { assert, strictAssert } from '../util/assert';
|
||||||
import { getProvisioningUrl } from '../util/getProvisioningUrl';
|
import { getProvisioningUrl } from '../util/getProvisioningUrl';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
|
@ -56,7 +58,7 @@ function getIdentifier(id: string | undefined) {
|
||||||
return parts[0];
|
return parts[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeneratedKeysType = {
|
export type GeneratedKeysType = {
|
||||||
preKeys: Array<{
|
preKeys: Array<{
|
||||||
keyId: number;
|
keyId: number;
|
||||||
publicKey: ArrayBuffer;
|
publicKey: ArrayBuffer;
|
||||||
|
@ -89,16 +91,10 @@ export default class AccountManager extends EventTarget {
|
||||||
return this.server.requestVerificationSMS(number);
|
return this.server.requestVerificationSMS(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
async encryptDeviceName(name: string, providedIdentityKey?: KeyPairType) {
|
async encryptDeviceName(name: string, identityKey: KeyPairType) {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return null;
|
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(
|
const encrypted = await window.Signal.Crypto.encryptDeviceName(
|
||||||
name,
|
name,
|
||||||
identityKey.pubKey
|
identityKey.pubKey
|
||||||
|
@ -114,7 +110,10 @@ export default class AccountManager extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
async decryptDeviceName(base64: string) {
|
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) {
|
if (!identityKey) {
|
||||||
throw new Error('decryptDeviceName: No identity key pair!');
|
throw new Error('decryptDeviceName: No identity key pair!');
|
||||||
}
|
}
|
||||||
|
@ -144,8 +143,19 @@ export default class AccountManager extends EventTarget {
|
||||||
if (isNameEncrypted) {
|
if (isNameEncrypted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const deviceName = window.textsecure.storage.user.getDeviceName();
|
const { storage } = window.textsecure;
|
||||||
const base64 = await this.encryptDeviceName(deviceName || '');
|
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) {
|
if (base64) {
|
||||||
await this.server.updateDeviceName(base64);
|
await this.server.updateDeviceName(base64);
|
||||||
|
@ -310,6 +320,7 @@ export default class AccountManager extends EventTarget {
|
||||||
|
|
||||||
async rotateSignedPreKey() {
|
async rotateSignedPreKey() {
|
||||||
return this.queueTask(async () => {
|
return this.queueTask(async () => {
|
||||||
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
|
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
|
||||||
if (typeof signedKeyId !== 'number') {
|
if (typeof signedKeyId !== 'number') {
|
||||||
throw new Error('Invalid signedKeyId');
|
throw new Error('Invalid signedKeyId');
|
||||||
|
@ -318,7 +329,7 @@ export default class AccountManager extends EventTarget {
|
||||||
const store = window.textsecure.storage.protocol;
|
const store = window.textsecure.storage.protocol;
|
||||||
const { server, cleanSignedPreKeys } = this;
|
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));
|
existingKeys.sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
|
||||||
const confirmedKeys = existingKeys.filter(key => key.confirmed);
|
const confirmedKeys = existingKeys.filter(key => key.confirmed);
|
||||||
const mostRecent = confirmedKeys[0];
|
const mostRecent = confirmedKeys[0];
|
||||||
|
@ -332,7 +343,7 @@ export default class AccountManager extends EventTarget {
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
return store
|
return store
|
||||||
.getIdentityKeyPair()
|
.getIdentityKeyPair(ourUuid)
|
||||||
.then(
|
.then(
|
||||||
async (identityKey: KeyPairType | undefined) => {
|
async (identityKey: KeyPairType | undefined) => {
|
||||||
if (!identityKey) {
|
if (!identityKey) {
|
||||||
|
@ -357,7 +368,7 @@ export default class AccountManager extends EventTarget {
|
||||||
window.log.info('Saving new signed prekey', res.keyId);
|
window.log.info('Saving new signed prekey', res.keyId);
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
window.textsecure.storage.put('signedKeyId', signedKeyId + 1),
|
window.textsecure.storage.put('signedKeyId', signedKeyId + 1),
|
||||||
store.storeSignedPreKey(res.keyId, res.keyPair),
|
store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair),
|
||||||
server.setSignedPreKey({
|
server.setSignedPreKey({
|
||||||
keyId: res.keyId,
|
keyId: res.keyId,
|
||||||
publicKey: res.keyPair.pubKey,
|
publicKey: res.keyPair.pubKey,
|
||||||
|
@ -369,7 +380,12 @@ export default class AccountManager extends EventTarget {
|
||||||
window.log.info('Confirming new signed prekey', res.keyId);
|
window.log.info('Confirming new signed prekey', res.keyId);
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
window.textsecure.storage.remove('signedKeyRotationRejected'),
|
window.textsecure.storage.remove('signedKeyRotationRejected'),
|
||||||
store.storeSignedPreKey(res.keyId, res.keyPair, confirmed),
|
store.storeSignedPreKey(
|
||||||
|
ourUuid,
|
||||||
|
res.keyId,
|
||||||
|
res.keyPair,
|
||||||
|
confirmed
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
.then(cleanSignedPreKeys);
|
.then(cleanSignedPreKeys);
|
||||||
|
@ -409,9 +425,10 @@ export default class AccountManager extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanSignedPreKeys() {
|
async cleanSignedPreKeys() {
|
||||||
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const store = window.textsecure.storage.protocol;
|
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));
|
allKeys.sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
|
||||||
const confirmed = allKeys.filter(key => key.confirmed);
|
const confirmed = allKeys.filter(key => key.confirmed);
|
||||||
const unconfirmed = 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(
|
window.log.info(
|
||||||
`Removing signed prekey: ${key.keyId} with timestamp ${timestamp}${confirmedText}`
|
`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,
|
readReceipts?: boolean | null,
|
||||||
options: { accessKey?: ArrayBuffer; uuid?: string } = {}
|
options: { accessKey?: ArrayBuffer; uuid?: string } = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const { storage } = window.textsecure;
|
||||||
const { accessKey, uuid } = options;
|
const { accessKey, uuid } = options;
|
||||||
let password = btoa(utils.getString(getRandomBytes(16)));
|
let password = btoa(utils.getString(getRandomBytes(16)));
|
||||||
password = password.substring(0, password.length - 2);
|
password = password.substring(0, password.length - 2);
|
||||||
const registrationId = generateRegistrationId();
|
const registrationId = generateRegistrationId();
|
||||||
|
|
||||||
const previousNumber = getIdentifier(
|
const previousNumber = getIdentifier(storage.get('number_id'));
|
||||||
window.textsecure.storage.get('number_id')
|
const previousUuid = getIdentifier(storage.get('uuid_id'));
|
||||||
);
|
|
||||||
const previousUuid = getIdentifier(
|
|
||||||
window.textsecure.storage.get('uuid_id')
|
|
||||||
);
|
|
||||||
|
|
||||||
let encryptedDeviceName;
|
let encryptedDeviceName;
|
||||||
if (deviceName) {
|
if (deviceName) {
|
||||||
|
@ -500,7 +514,10 @@ export default class AccountManager extends EventTarget {
|
||||||
{ accessKey, uuid }
|
{ 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
|
// We only consider the number changed if we didn't have a UUID before
|
||||||
const numberChanged =
|
const numberChanged =
|
||||||
|
@ -519,7 +536,7 @@ export default class AccountManager extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.textsecure.storage.protocol.removeAllData();
|
await storage.protocol.removeAllData();
|
||||||
window.log.info('Successfully deleted previous data');
|
window.log.info('Successfully deleted previous data');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
|
@ -530,23 +547,34 @@ export default class AccountManager extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
window.textsecure.storage.remove('identityKey'),
|
storage.user.removeCredentials(),
|
||||||
window.textsecure.storage.user.removeCredentials(),
|
storage.remove('regionCode'),
|
||||||
window.textsecure.storage.remove('registrationId'),
|
storage.remove('userAgent'),
|
||||||
window.textsecure.storage.remove('regionCode'),
|
storage.remove('profileKey'),
|
||||||
window.textsecure.storage.remove('userAgent'),
|
storage.remove('read-receipt-setting'),
|
||||||
window.textsecure.storage.remove('profileKey'),
|
|
||||||
window.textsecure.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
|
// `setCredentials` needs to be called
|
||||||
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
|
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
|
||||||
// indirectly calls `ConversationController.getConverationId()` which
|
// indirectly calls `ConversationController.getConverationId()` which
|
||||||
// initializes the conversation for the given number (our number) 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
|
// calls out to the user storage API to get the stored UUID and number
|
||||||
// information.
|
// information.
|
||||||
await window.textsecure.storage.user.setCredentials({
|
await storage.user.setCredentials({
|
||||||
uuid,
|
uuid: ourUuid,
|
||||||
number,
|
number,
|
||||||
deviceId: response.deviceId ?? 1,
|
deviceId: response.deviceId ?? 1,
|
||||||
deviceName: deviceName ?? undefined,
|
deviceName: deviceName ?? undefined,
|
||||||
|
@ -558,7 +586,7 @@ export default class AccountManager extends EventTarget {
|
||||||
// below.
|
// below.
|
||||||
const conversationId = window.ConversationController.ensureContactIds({
|
const conversationId = window.ConversationController.ensureContactIds({
|
||||||
e164: number,
|
e164: number,
|
||||||
uuid,
|
uuid: ourUuid,
|
||||||
highTrust: true,
|
highTrust: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -568,36 +596,42 @@ export default class AccountManager extends EventTarget {
|
||||||
|
|
||||||
// update our own identity key, which may have changed
|
// update our own identity key, which may have changed
|
||||||
// if we're relinking after a reinstall on the master device
|
// if we're relinking after a reinstall on the master device
|
||||||
await window.textsecure.storage.protocol.saveIdentityWithAttributes(
|
await storage.protocol.saveIdentityWithAttributes(ourUuid, {
|
||||||
uuid || number,
|
publicKey: identityKeyPair.pubKey,
|
||||||
{
|
firstUse: true,
|
||||||
publicKey: identityKeyPair.pubKey,
|
timestamp: Date.now(),
|
||||||
firstUse: true,
|
verified: storage.protocol.VerifiedStatus.VERIFIED,
|
||||||
timestamp: Date.now(),
|
nonblockingApproval: true,
|
||||||
verified: window.textsecure.storage.protocol.VerifiedStatus.VERIFIED,
|
});
|
||||||
nonblockingApproval: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await window.textsecure.storage.put('identityKey', identityKeyPair);
|
const identityKeyMap = {
|
||||||
await window.textsecure.storage.put('registrationId', registrationId);
|
...(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) {
|
if (profileKey) {
|
||||||
await ourProfileKeyService.set(profileKey);
|
await ourProfileKeyService.set(profileKey);
|
||||||
}
|
}
|
||||||
if (userAgent) {
|
if (userAgent) {
|
||||||
await window.textsecure.storage.put('userAgent', userAgent);
|
await storage.put('userAgent', userAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
await window.textsecure.storage.put(
|
await storage.put('read-receipt-setting', Boolean(readReceipts));
|
||||||
'read-receipt-setting',
|
|
||||||
Boolean(readReceipts)
|
|
||||||
);
|
|
||||||
|
|
||||||
const regionCode = window.libphonenumber.util.getRegionCodeForNumber(
|
const regionCode = window.libphonenumber.util.getRegionCodeForNumber(
|
||||||
number
|
number
|
||||||
);
|
);
|
||||||
await window.textsecure.storage.put('regionCode', regionCode);
|
await storage.put('regionCode', regionCode);
|
||||||
await window.textsecure.storage.protocol.hydrateCaches();
|
await storage.protocol.hydrateCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearSessionsAndPreKeys() {
|
async clearSessionsAndPreKeys() {
|
||||||
|
@ -626,7 +660,8 @@ export default class AccountManager extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.log.info('confirmKeys: confirming key', key.keyId);
|
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) {
|
async generateKeys(count: number, providedProgressCallback?: Function) {
|
||||||
|
@ -636,6 +671,7 @@ export default class AccountManager extends EventTarget {
|
||||||
: null;
|
: null;
|
||||||
const startId = window.textsecure.storage.get('maxPreKeyId', 1);
|
const startId = window.textsecure.storage.get('maxPreKeyId', 1);
|
||||||
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
|
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
|
||||||
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
|
|
||||||
if (typeof startId !== 'number') {
|
if (typeof startId !== 'number') {
|
||||||
throw new Error('Invalid maxPreKeyId');
|
throw new Error('Invalid maxPreKeyId');
|
||||||
|
@ -645,7 +681,7 @@ export default class AccountManager extends EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = window.textsecure.storage.protocol;
|
const store = window.textsecure.storage.protocol;
|
||||||
return store.getIdentityKeyPair().then(async identityKey => {
|
return store.getIdentityKeyPair(ourUuid).then(async identityKey => {
|
||||||
if (!identityKey) {
|
if (!identityKey) {
|
||||||
throw new Error('generateKeys: No identity key pair!');
|
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) {
|
for (let keyId = startId; keyId < startId + count; keyId += 1) {
|
||||||
promises.push(
|
promises.push(
|
||||||
Promise.resolve(generatePreKey(keyId)).then(async res => {
|
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({
|
result.preKeys.push({
|
||||||
keyId: res.keyId,
|
keyId: res.keyId,
|
||||||
publicKey: res.keyPair.pubKey,
|
publicKey: res.keyPair.pubKey,
|
||||||
|
@ -674,7 +710,7 @@ export default class AccountManager extends EventTarget {
|
||||||
promises.push(
|
promises.push(
|
||||||
Promise.resolve(generateSignedPreKey(identityKey, signedKeyId)).then(
|
Promise.resolve(generateSignedPreKey(identityKey, signedKeyId)).then(
|
||||||
async res => {
|
async res => {
|
||||||
await store.storeSignedPreKey(res.keyId, res.keyPair);
|
await store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair);
|
||||||
result.signedPreKey = {
|
result.signedPreKey = {
|
||||||
keyId: res.keyId,
|
keyId: res.keyId,
|
||||||
publicKey: res.keyPair.pubKey,
|
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 { Zone } from '../util/Zone';
|
||||||
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
|
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
|
||||||
import { DownloadedAttachmentType } from '../types/Attachment';
|
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 * as Errors from '../types/errors';
|
||||||
|
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
@ -754,8 +757,9 @@ export default class MessageReceiver
|
||||||
pendingSessions: true,
|
pendingSessions: true,
|
||||||
pendingUnprocessed: true,
|
pendingUnprocessed: true,
|
||||||
});
|
});
|
||||||
const sessionStore = new Sessions({ zone });
|
const ourUuid = this.storage.user.getCheckedUuid();
|
||||||
const identityKeyStore = new IdentityKeys({ zone });
|
const sessionStore = new Sessions({ zone, ourUuid });
|
||||||
|
const identityKeyStore = new IdentityKeys({ zone, ourUuid });
|
||||||
const failed: Array<UnprocessedType> = [];
|
const failed: Array<UnprocessedType> = [];
|
||||||
|
|
||||||
// Below we:
|
// Below we:
|
||||||
|
@ -1228,18 +1232,12 @@ export default class MessageReceiver
|
||||||
ciphertext: Uint8Array
|
ciphertext: Uint8Array
|
||||||
): Promise<DecryptSealedSenderResult> {
|
): Promise<DecryptSealedSenderResult> {
|
||||||
const localE164 = this.storage.user.getNumber();
|
const localE164 = this.storage.user.getNumber();
|
||||||
const localUuid = this.storage.user.getUuid();
|
const ourUuid = this.storage.user.getCheckedUuid();
|
||||||
const localDeviceId = parseIntOrThrow(
|
const localDeviceId = parseIntOrThrow(
|
||||||
this.storage.user.getDeviceId(),
|
this.storage.user.getDeviceId(),
|
||||||
'MessageReceiver.decryptSealedSender: localDeviceId'
|
'MessageReceiver.decryptSealedSender: localDeviceId'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!localUuid) {
|
|
||||||
throw new Error(
|
|
||||||
'MessageReceiver.decryptSealedSender: Failed to fetch local UUID'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const logId = this.getEnvelopeId(envelope);
|
const logId = this.getEnvelopeId(envelope);
|
||||||
|
|
||||||
const { unsealedContent: messageContent, certificate } = envelope;
|
const { unsealedContent: messageContent, certificate } = envelope;
|
||||||
|
@ -1280,9 +1278,12 @@ export default class MessageReceiver
|
||||||
);
|
);
|
||||||
const sealedSenderIdentifier = certificate.senderUuid();
|
const sealedSenderIdentifier = certificate.senderUuid();
|
||||||
const sealedSenderSourceDevice = certificate.senderDeviceId();
|
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(
|
const plaintext = await this.storage.protocol.enqueueSenderKeyJob(
|
||||||
address,
|
address,
|
||||||
|
@ -1305,11 +1306,22 @@ export default class MessageReceiver
|
||||||
'unidentified message/passing to sealedSenderDecryptMessage'
|
'unidentified message/passing to sealedSenderDecryptMessage'
|
||||||
);
|
);
|
||||||
|
|
||||||
const preKeyStore = new PreKeys();
|
const preKeyStore = new PreKeys({ ourUuid });
|
||||||
const signedPreKeyStore = new SignedPreKeys();
|
const signedPreKeyStore = new SignedPreKeys({ ourUuid });
|
||||||
|
|
||||||
const sealedSenderIdentifier = envelope.sourceUuid || envelope.source;
|
const sealedSenderIdentifier = envelope.sourceUuid;
|
||||||
const address = `${sealedSenderIdentifier}.${envelope.sourceDevice}`;
|
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(
|
const unsealedPlaintext = await this.storage.protocol.enqueueSessionJob(
|
||||||
address,
|
address,
|
||||||
() =>
|
() =>
|
||||||
|
@ -1318,7 +1330,7 @@ export default class MessageReceiver
|
||||||
PublicKey.deserialize(Buffer.from(this.serverTrustRoot)),
|
PublicKey.deserialize(Buffer.from(this.serverTrustRoot)),
|
||||||
envelope.serverTimestamp,
|
envelope.serverTimestamp,
|
||||||
localE164 || null,
|
localE164 || null,
|
||||||
localUuid,
|
ourUuid.toString(),
|
||||||
localDeviceId,
|
localDeviceId,
|
||||||
sessionStore,
|
sessionStore,
|
||||||
identityKeyStore,
|
identityKeyStore,
|
||||||
|
@ -1341,11 +1353,20 @@ export default class MessageReceiver
|
||||||
const logId = this.getEnvelopeId(envelope);
|
const logId = this.getEnvelopeId(envelope);
|
||||||
const envelopeTypeEnum = Proto.Envelope.Type;
|
const envelopeTypeEnum = Proto.Envelope.Type;
|
||||||
|
|
||||||
const identifier = envelope.sourceUuid || envelope.source;
|
const identifier = envelope.sourceUuid;
|
||||||
const { sourceDevice } = envelope;
|
const { sourceDevice } = envelope;
|
||||||
|
|
||||||
const preKeyStore = new PreKeys();
|
const ourUuid = this.storage.user.getCheckedUuid();
|
||||||
const signedPreKeyStore = new SignedPreKeys();
|
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) {
|
if (envelope.type === envelopeTypeEnum.PLAINTEXT_CONTENT) {
|
||||||
window.log.info(`decrypt/${logId}: plaintext message`);
|
window.log.info(`decrypt/${logId}: plaintext message`);
|
||||||
|
@ -1368,7 +1389,6 @@ export default class MessageReceiver
|
||||||
}
|
}
|
||||||
const signalMessage = SignalMessage.deserialize(Buffer.from(ciphertext));
|
const signalMessage = SignalMessage.deserialize(Buffer.from(ciphertext));
|
||||||
|
|
||||||
const address = `${identifier}.${sourceDevice}`;
|
|
||||||
const plaintext = await this.storage.protocol.enqueueSessionJob(
|
const plaintext = await this.storage.protocol.enqueueSessionJob(
|
||||||
address,
|
address,
|
||||||
async () =>
|
async () =>
|
||||||
|
@ -1400,7 +1420,6 @@ export default class MessageReceiver
|
||||||
Buffer.from(ciphertext)
|
Buffer.from(ciphertext)
|
||||||
);
|
);
|
||||||
|
|
||||||
const address = `${identifier}.${sourceDevice}`;
|
|
||||||
const plaintext = await this.storage.protocol.enqueueSessionJob(
|
const plaintext = await this.storage.protocol.enqueueSessionJob(
|
||||||
address,
|
address,
|
||||||
async () =>
|
async () =>
|
||||||
|
@ -1562,7 +1581,7 @@ export default class MessageReceiver
|
||||||
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
||||||
const { source, sourceUuid } = envelope;
|
const { source, sourceUuid } = envelope;
|
||||||
const ourE164 = this.storage.user.getNumber();
|
const ourE164 = this.storage.user.getNumber();
|
||||||
const ourUuid = this.storage.user.getUuid();
|
const ourUuid = this.storage.user.getCheckedUuid().toString();
|
||||||
const isMe =
|
const isMe =
|
||||||
(source && ourE164 && source === ourE164) ||
|
(source && ourE164 && source === ourE164) ||
|
||||||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||||
|
@ -1613,7 +1632,7 @@ export default class MessageReceiver
|
||||||
);
|
);
|
||||||
let p: Promise<void> = Promise.resolve();
|
let p: Promise<void> = Promise.resolve();
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
const destination = envelope.sourceUuid || envelope.source;
|
const destination = envelope.sourceUuid;
|
||||||
if (!destination) {
|
if (!destination) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'MessageReceiver.handleDataMessage: source and sourceUuid were falsey'
|
'MessageReceiver.handleDataMessage: source and sourceUuid were falsey'
|
||||||
|
@ -1651,7 +1670,7 @@ export default class MessageReceiver
|
||||||
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
const isBlocked = groupId ? this.isGroupBlocked(groupId) : false;
|
||||||
const { source, sourceUuid } = envelope;
|
const { source, sourceUuid } = envelope;
|
||||||
const ourE164 = this.storage.user.getNumber();
|
const ourE164 = this.storage.user.getNumber();
|
||||||
const ourUuid = this.storage.user.getUuid();
|
const ourUuid = this.storage.user.getCheckedUuid().toString();
|
||||||
const isMe =
|
const isMe =
|
||||||
(source && ourE164 && source === ourE164) ||
|
(source && ourE164 && source === ourE164) ||
|
||||||
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||||
|
@ -1711,8 +1730,7 @@ export default class MessageReceiver
|
||||||
}
|
}
|
||||||
|
|
||||||
const { timestamp } = envelope;
|
const { timestamp } = envelope;
|
||||||
const identifier =
|
const identifier = envelope.groupId || envelope.sourceUuid;
|
||||||
envelope.groupId || envelope.sourceUuid || envelope.source;
|
|
||||||
const conversation = window.ConversationController.get(identifier);
|
const conversation = window.ConversationController.get(identifier);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1848,7 +1866,7 @@ export default class MessageReceiver
|
||||||
// Note: we don't call removeFromCache here because this message can be combined
|
// Note: we don't call removeFromCache here because this message can be combined
|
||||||
// with a dataMessage, for example. That processing will dictate cache removal.
|
// with a dataMessage, for example. That processing will dictate cache removal.
|
||||||
|
|
||||||
const identifier = envelope.sourceUuid || envelope.source;
|
const identifier = envelope.sourceUuid;
|
||||||
const { sourceDevice } = envelope;
|
const { sourceDevice } = envelope;
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -1865,8 +1883,12 @@ export default class MessageReceiver
|
||||||
const senderKeyDistributionMessage = SenderKeyDistributionMessage.deserialize(
|
const senderKeyDistributionMessage = SenderKeyDistributionMessage.deserialize(
|
||||||
Buffer.from(distributionMessage)
|
Buffer.from(distributionMessage)
|
||||||
);
|
);
|
||||||
const senderKeyStore = new SenderKeys();
|
const ourUuid = this.storage.user.getCheckedUuid();
|
||||||
const address = `${identifier}.${sourceDevice}`;
|
const senderKeyStore = new SenderKeys({ ourUuid });
|
||||||
|
const address = new QualifiedAddress(
|
||||||
|
ourUuid,
|
||||||
|
Address.create(identifier, sourceDevice)
|
||||||
|
);
|
||||||
|
|
||||||
await this.storage.protocol.enqueueSenderKeyJob(
|
await this.storage.protocol.enqueueSenderKeyJob(
|
||||||
address,
|
address,
|
||||||
|
@ -2109,11 +2131,11 @@ export default class MessageReceiver
|
||||||
syncMessage: ProcessedSyncMessage
|
syncMessage: ProcessedSyncMessage
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const ourNumber = this.storage.user.getNumber();
|
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 fromSelfSource = envelope.source && envelope.source === ourNumber;
|
||||||
const fromSelfSourceUuid =
|
const fromSelfSourceUuid =
|
||||||
envelope.sourceUuid && envelope.sourceUuid === ourUuid;
|
envelope.sourceUuid && envelope.sourceUuid === ourUuid.toString();
|
||||||
if (!fromSelfSource && !fromSelfSourceUuid) {
|
if (!fromSelfSource && !fromSelfSourceUuid) {
|
||||||
throw new Error('Received sync message from another number');
|
throw new Error('Received sync message from another number');
|
||||||
}
|
}
|
||||||
|
@ -2536,8 +2558,14 @@ export default class MessageReceiver
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleEndSession(identifier: string): Promise<void> {
|
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}`);
|
window.log.info(`handleEndSession: closing sessions for ${identifier}`);
|
||||||
await this.storage.protocol.archiveAllSessions(identifier);
|
await this.storage.protocol.archiveAllSessions(theirUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processDecrypted(
|
private async processDecrypted(
|
||||||
|
|
|
@ -32,6 +32,9 @@ import {
|
||||||
} from './Errors';
|
} from './Errors';
|
||||||
import { CallbackResultType, CustomError } from './Types.d';
|
import { CallbackResultType, CustomError } from './Types.d';
|
||||||
import { isValidNumber } from '../types/PhoneNumber';
|
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 { Sessions, IdentityKeys } from '../LibSignalStores';
|
||||||
import { typedArrayToArrayBuffer as toArrayBuffer } from '../Crypto';
|
import { typedArrayToArrayBuffer as toArrayBuffer } from '../Crypto';
|
||||||
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
||||||
|
@ -237,9 +240,11 @@ export default class OutgoingMessage {
|
||||||
recurse?: boolean
|
recurse?: boolean
|
||||||
): () => Promise<void> {
|
): () => Promise<void> {
|
||||||
return async () => {
|
return async () => {
|
||||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds(
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
identifier
|
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
|
||||||
);
|
ourUuid,
|
||||||
|
identifier,
|
||||||
|
});
|
||||||
if (deviceIds.length === 0) {
|
if (deviceIds.length === 0) {
|
||||||
this.registerError(
|
this.registerError(
|
||||||
identifier,
|
identifier,
|
||||||
|
@ -386,9 +391,12 @@ export default class OutgoingMessage {
|
||||||
|
|
||||||
// We don't send to ourselves unless sealedSender is enabled
|
// We don't send to ourselves unless sealedSender is enabled
|
||||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
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();
|
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||||
if ((identifier === ourNumber || identifier === ourUuid) && !sealedSender) {
|
if (
|
||||||
|
(identifier === ourNumber || identifier === ourUuid.toString()) &&
|
||||||
|
!sealedSender
|
||||||
|
) {
|
||||||
deviceIds = reject(
|
deviceIds = reject(
|
||||||
deviceIds,
|
deviceIds,
|
||||||
deviceId =>
|
deviceId =>
|
||||||
|
@ -399,18 +407,22 @@ export default class OutgoingMessage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionStore = new Sessions();
|
const sessionStore = new Sessions({ ourUuid });
|
||||||
const identityKeyStore = new IdentityKeys();
|
const identityKeyStore = new IdentityKeys({ ourUuid });
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
deviceIds.map(async destinationDeviceId => {
|
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>(
|
return window.textsecure.storage.protocol.enqueueSessionJob<SendMetadata>(
|
||||||
address,
|
address,
|
||||||
async () => {
|
async () => {
|
||||||
const protocolAddress = ProtocolAddress.new(
|
const protocolAddress = ProtocolAddress.new(
|
||||||
identifier,
|
theirUuid.toString(),
|
||||||
destinationDeviceId
|
destinationDeviceId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -566,7 +578,10 @@ export default class OutgoingMessage {
|
||||||
p = Promise.all(
|
p = Promise.all(
|
||||||
error.response.staleDevices.map(async (deviceId: number) => {
|
error.response.staleDevices.map(async (deviceId: number) => {
|
||||||
await window.textsecure.storage.protocol.archiveSession(
|
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.log.info('closing all sessions for', identifier);
|
||||||
window.textsecure.storage.protocol
|
window.textsecure.storage.protocol
|
||||||
.archiveAllSessions(identifier)
|
.archiveAllSessions(UUID.checkedLookup(identifier))
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -623,10 +638,13 @@ export default class OutgoingMessage {
|
||||||
identifier: string,
|
identifier: string,
|
||||||
deviceIdsToRemove: Array<number>
|
deviceIdsToRemove: Array<number>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
|
const theirUuid = UUID.checkedLookup(identifier);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
deviceIdsToRemove.map(async deviceId => {
|
deviceIdsToRemove.map(async deviceId => {
|
||||||
await window.textsecure.storage.protocol.archiveSession(
|
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(
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
identifier
|
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
|
||||||
);
|
ourUuid,
|
||||||
|
identifier,
|
||||||
|
});
|
||||||
if (deviceIds.length === 0) {
|
if (deviceIds.length === 0) {
|
||||||
await this.getKeysForIdentifier(identifier);
|
await this.getKeysForIdentifier(identifier);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ import {
|
||||||
|
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
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 { SenderKeys } from '../LibSignalStores';
|
||||||
import {
|
import {
|
||||||
ChallengeType,
|
ChallengeType,
|
||||||
|
@ -737,14 +740,14 @@ export default class MessageSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
const myE164 = window.textsecure.storage.user.getNumber();
|
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 || [];
|
const groupMembers = groupV2?.members || groupV1?.members || [];
|
||||||
|
|
||||||
// We should always have a UUID but have this check just in case we don't.
|
// We should always have a UUID but have this check just in case we don't.
|
||||||
let isNotMe: (recipient: string) => boolean;
|
let isNotMe: (recipient: string) => boolean;
|
||||||
if (myUuid) {
|
if (myUuid) {
|
||||||
isNotMe = r => r !== myE164 && r !== myUuid;
|
isNotMe = r => r !== myE164 && r !== myUuid.toString();
|
||||||
} else {
|
} else {
|
||||||
isNotMe = r => r !== myE164;
|
isNotMe = r => r !== myE164;
|
||||||
}
|
}
|
||||||
|
@ -1030,8 +1033,7 @@ export default class MessageSender {
|
||||||
isUpdate?: boolean;
|
isUpdate?: boolean;
|
||||||
options?: SendOptionsType;
|
options?: SendOptionsType;
|
||||||
}>): Promise<CallbackResultType> {
|
}>): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
|
|
||||||
const dataMessage = Proto.DataMessage.decode(
|
const dataMessage = Proto.DataMessage.decode(
|
||||||
new FIXMEU8(encodedDataMessage)
|
new FIXMEU8(encodedDataMessage)
|
||||||
|
@ -1086,7 +1088,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp,
|
timestamp,
|
||||||
contentHint: ContentHint.RESENDABLE,
|
contentHint: ContentHint.RESENDABLE,
|
||||||
|
@ -1097,8 +1099,7 @@ export default class MessageSender {
|
||||||
async sendRequestBlockSyncMessage(
|
async sendRequestBlockSyncMessage(
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
|
|
||||||
const request = new Proto.SyncMessage.Request();
|
const request = new Proto.SyncMessage.Request();
|
||||||
request.type = Proto.SyncMessage.Request.Type.BLOCKED;
|
request.type = Proto.SyncMessage.Request.Type.BLOCKED;
|
||||||
|
@ -1110,7 +1111,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.IMPLICIT,
|
contentHint: ContentHint.IMPLICIT,
|
||||||
|
@ -1121,8 +1122,7 @@ export default class MessageSender {
|
||||||
async sendRequestConfigurationSyncMessage(
|
async sendRequestConfigurationSyncMessage(
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
|
|
||||||
const request = new Proto.SyncMessage.Request();
|
const request = new Proto.SyncMessage.Request();
|
||||||
request.type = Proto.SyncMessage.Request.Type.CONFIGURATION;
|
request.type = Proto.SyncMessage.Request.Type.CONFIGURATION;
|
||||||
|
@ -1134,7 +1134,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.IMPLICIT,
|
contentHint: ContentHint.IMPLICIT,
|
||||||
|
@ -1145,8 +1145,7 @@ export default class MessageSender {
|
||||||
async sendRequestGroupSyncMessage(
|
async sendRequestGroupSyncMessage(
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
|
|
||||||
const request = new Proto.SyncMessage.Request();
|
const request = new Proto.SyncMessage.Request();
|
||||||
request.type = Proto.SyncMessage.Request.Type.GROUPS;
|
request.type = Proto.SyncMessage.Request.Type.GROUPS;
|
||||||
|
@ -1158,7 +1157,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.IMPLICIT,
|
contentHint: ContentHint.IMPLICIT,
|
||||||
|
@ -1169,8 +1168,7 @@ export default class MessageSender {
|
||||||
async sendRequestContactSyncMessage(
|
async sendRequestContactSyncMessage(
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
|
|
||||||
const request = new Proto.SyncMessage.Request();
|
const request = new Proto.SyncMessage.Request();
|
||||||
request.type = Proto.SyncMessage.Request.Type.CONTACTS;
|
request.type = Proto.SyncMessage.Request.Type.CONTACTS;
|
||||||
|
@ -1182,7 +1180,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.IMPLICIT,
|
contentHint: ContentHint.IMPLICIT,
|
||||||
|
@ -1193,8 +1191,7 @@ export default class MessageSender {
|
||||||
async sendFetchManifestSyncMessage(
|
async sendFetchManifestSyncMessage(
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
|
||||||
|
|
||||||
const fetchLatest = new Proto.SyncMessage.FetchLatest();
|
const fetchLatest = new Proto.SyncMessage.FetchLatest();
|
||||||
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST;
|
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.STORAGE_MANIFEST;
|
||||||
|
@ -1207,7 +1204,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.IMPLICIT,
|
contentHint: ContentHint.IMPLICIT,
|
||||||
|
@ -1218,8 +1215,7 @@ export default class MessageSender {
|
||||||
async sendFetchLocalProfileSyncMessage(
|
async sendFetchLocalProfileSyncMessage(
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
|
||||||
|
|
||||||
const fetchLatest = new Proto.SyncMessage.FetchLatest();
|
const fetchLatest = new Proto.SyncMessage.FetchLatest();
|
||||||
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.LOCAL_PROFILE;
|
fetchLatest.type = Proto.SyncMessage.FetchLatest.Type.LOCAL_PROFILE;
|
||||||
|
@ -1232,7 +1228,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.IMPLICIT,
|
contentHint: ContentHint.IMPLICIT,
|
||||||
|
@ -1243,8 +1239,7 @@ export default class MessageSender {
|
||||||
async sendRequestKeySyncMessage(
|
async sendRequestKeySyncMessage(
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
|
||||||
|
|
||||||
const request = new Proto.SyncMessage.Request();
|
const request = new Proto.SyncMessage.Request();
|
||||||
request.type = Proto.SyncMessage.Request.Type.KEYS;
|
request.type = Proto.SyncMessage.Request.Type.KEYS;
|
||||||
|
@ -1257,7 +1252,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.IMPLICIT,
|
contentHint: ContentHint.IMPLICIT,
|
||||||
|
@ -1273,8 +1268,7 @@ export default class MessageSender {
|
||||||
}>,
|
}>,
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.read = [];
|
syncMessage.read = [];
|
||||||
|
@ -1289,7 +1283,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.RESENDABLE,
|
contentHint: ContentHint.RESENDABLE,
|
||||||
|
@ -1305,8 +1299,7 @@ export default class MessageSender {
|
||||||
}>,
|
}>,
|
||||||
options?: SendOptionsType
|
options?: SendOptionsType
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
syncMessage.viewed = views.map(view => new Proto.SyncMessage.Viewed(view));
|
syncMessage.viewed = views.map(view => new Proto.SyncMessage.Viewed(view));
|
||||||
|
@ -1316,7 +1309,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.RESENDABLE,
|
contentHint: ContentHint.RESENDABLE,
|
||||||
|
@ -1330,8 +1323,7 @@ export default class MessageSender {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
|
|
||||||
|
@ -1349,7 +1341,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.RESENDABLE,
|
contentHint: ContentHint.RESENDABLE,
|
||||||
|
@ -1366,8 +1358,7 @@ export default class MessageSender {
|
||||||
}>,
|
}>,
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
|
|
||||||
|
@ -1390,7 +1381,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.RESENDABLE,
|
contentHint: ContentHint.RESENDABLE,
|
||||||
|
@ -1406,8 +1397,7 @@ export default class MessageSender {
|
||||||
}>,
|
}>,
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
const ENUM = Proto.SyncMessage.StickerPackOperation.Type;
|
const ENUM = Proto.SyncMessage.StickerPackOperation.Type;
|
||||||
|
|
||||||
const packOperations = operations.map(item => {
|
const packOperations = operations.map(item => {
|
||||||
|
@ -1430,7 +1420,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: contentMessage,
|
proto: contentMessage,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
contentHint: ContentHint.IMPLICIT,
|
contentHint: ContentHint.IMPLICIT,
|
||||||
|
@ -1445,8 +1435,7 @@ export default class MessageSender {
|
||||||
identityKey: Readonly<ArrayBuffer>,
|
identityKey: Readonly<ArrayBuffer>,
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const myUuid = window.textsecure.storage.user.getUuid();
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
if (!destinationE164 && !destinationUuid) {
|
if (!destinationE164 && !destinationUuid) {
|
||||||
|
@ -1488,7 +1477,7 @@ export default class MessageSender {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
return this.sendIndividualProto({
|
return this.sendIndividualProto({
|
||||||
identifier: myUuid || myNumber,
|
identifier: myUuid.toString(),
|
||||||
proto: secondMessage,
|
proto: secondMessage,
|
||||||
timestamp: now,
|
timestamp: now,
|
||||||
contentHint: ContentHint.RESENDABLE,
|
contentHint: ContentHint.RESENDABLE,
|
||||||
|
@ -1675,6 +1664,7 @@ export default class MessageSender {
|
||||||
proto.timestamp = timestamp;
|
proto.timestamp = timestamp;
|
||||||
|
|
||||||
const identifier = uuid || e164;
|
const identifier = uuid || e164;
|
||||||
|
const theirUuid = UUID.checkedLookup(identifier);
|
||||||
|
|
||||||
const logError = (prefix: string) => (error: Error) => {
|
const logError = (prefix: string) => (error: Error) => {
|
||||||
window.log.error(prefix, error && error.stack ? error.stack : 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 { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
const sendToContactPromise = window.textsecure.storage.protocol
|
const sendToContactPromise = window.textsecure.storage.protocol
|
||||||
.archiveAllSessions(identifier)
|
.archiveAllSessions(theirUuid)
|
||||||
.catch(logError('resetSession/archiveAllSessions1 error:'))
|
.catch(logError('resetSession/archiveAllSessions1 error:'))
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
|
@ -1706,14 +1696,14 @@ export default class MessageSender {
|
||||||
})
|
})
|
||||||
.then(async result => {
|
.then(async result => {
|
||||||
await window.textsecure.storage.protocol
|
await window.textsecure.storage.protocol
|
||||||
.archiveAllSessions(identifier)
|
.archiveAllSessions(theirUuid)
|
||||||
.catch(logError('resetSession/archiveAllSessions2 error:'));
|
.catch(logError('resetSession/archiveAllSessions2 error:'));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
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!
|
// We already sent the reset session to our other devices in the code above!
|
||||||
if ((e164 && e164 === myNumber) || (uuid && uuid === myUuid)) {
|
if ((e164 && e164 === myNumber) || (uuid && uuid === myUuid)) {
|
||||||
return sendToContactPromise;
|
return sendToContactPromise;
|
||||||
|
@ -1882,7 +1872,7 @@ export default class MessageSender {
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const myE164 = window.textsecure.storage.user.getNumber();
|
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);
|
const identifiers = recipients.filter(id => id !== myE164 && id !== myUuid);
|
||||||
|
|
||||||
if (identifiers.length === 0) {
|
if (identifiers.length === 0) {
|
||||||
|
@ -1921,20 +1911,21 @@ export default class MessageSender {
|
||||||
async getSenderKeyDistributionMessage(
|
async getSenderKeyDistributionMessage(
|
||||||
distributionId: string
|
distributionId: string
|
||||||
): Promise<SenderKeyDistributionMessage> {
|
): Promise<SenderKeyDistributionMessage> {
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
if (!ourUuid) {
|
|
||||||
throw new Error(
|
|
||||||
'getSenderKeyDistributionMessage: Failed to fetch our UUID!'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const ourDeviceId = parseIntOrThrow(
|
const ourDeviceId = parseIntOrThrow(
|
||||||
window.textsecure.storage.user.getDeviceId(),
|
window.textsecure.storage.user.getDeviceId(),
|
||||||
'getSenderKeyDistributionMessage'
|
'getSenderKeyDistributionMessage'
|
||||||
);
|
);
|
||||||
|
|
||||||
const protocolAddress = ProtocolAddress.new(ourUuid, ourDeviceId);
|
const protocolAddress = ProtocolAddress.new(
|
||||||
const address = `${ourUuid}.${ourDeviceId}`;
|
ourUuid.toString(),
|
||||||
const senderKeyStore = new SenderKeys();
|
ourDeviceId
|
||||||
|
);
|
||||||
|
const address = new QualifiedAddress(
|
||||||
|
ourUuid,
|
||||||
|
new Address(ourUuid, ourDeviceId)
|
||||||
|
);
|
||||||
|
const senderKeyStore = new SenderKeys({ ourUuid });
|
||||||
|
|
||||||
return window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
return window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
||||||
address,
|
address,
|
||||||
|
@ -2044,7 +2035,7 @@ export default class MessageSender {
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const myNumber = window.textsecure.storage.user.getNumber();
|
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(
|
const recipients = groupIdentifiers.filter(
|
||||||
identifier => identifier !== myNumber && identifier !== myUuid
|
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 {
|
export {
|
||||||
IdentityKeyType,
|
IdentityKeyType,
|
||||||
|
IdentityKeyIdType,
|
||||||
|
PreKeyIdType,
|
||||||
PreKeyType,
|
PreKeyType,
|
||||||
|
SenderKeyIdType,
|
||||||
SenderKeyType,
|
SenderKeyType,
|
||||||
|
SessionIdType,
|
||||||
SessionType,
|
SessionType,
|
||||||
|
SignedPreKeyIdType,
|
||||||
SignedPreKeyType,
|
SignedPreKeyType,
|
||||||
UnprocessedType,
|
UnprocessedType,
|
||||||
UnprocessedUpdateType,
|
UnprocessedUpdateType,
|
||||||
|
|
|
@ -10,6 +10,9 @@ import {
|
||||||
|
|
||||||
import { UnregisteredUserError } from './Errors';
|
import { UnregisteredUserError } from './Errors';
|
||||||
import { Sessions, IdentityKeys } from '../LibSignalStores';
|
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';
|
import { ServerKeysType, WebAPIType } from './WebAPI';
|
||||||
|
|
||||||
export async function getKeysForIdentifier(
|
export async function getKeysForIdentifier(
|
||||||
|
@ -32,7 +35,11 @@ export async function getKeysForIdentifier(
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name === 'HTTPError' && error.code === 404) {
|
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);
|
throw new UnregisteredUserError(identifier, error);
|
||||||
}
|
}
|
||||||
|
@ -72,8 +79,9 @@ async function handleServerKeys(
|
||||||
response: ServerKeysType,
|
response: ServerKeysType,
|
||||||
devicesToUpdate?: Array<number>
|
devicesToUpdate?: Array<number>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const sessionStore = new Sessions();
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const identityKeyStore = new IdentityKeys();
|
const sessionStore = new Sessions({ ourUuid });
|
||||||
|
const identityKeyStore = new IdentityKeys({ ourUuid });
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
response.devices.map(async device => {
|
response.devices.map(async device => {
|
||||||
|
@ -95,7 +103,11 @@ async function handleServerKeys(
|
||||||
`getKeysForIdentifier/${identifier}: Missing signed prekey for deviceId ${deviceId}`
|
`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 preKeyId = preKey?.keyId || null;
|
||||||
const preKeyObject = preKey
|
const preKeyObject = preKey
|
||||||
? PublicKey.deserialize(Buffer.from(preKey.publicKey))
|
? PublicKey.deserialize(Buffer.from(preKey.publicKey))
|
||||||
|
@ -118,7 +130,10 @@ async function handleServerKeys(
|
||||||
identityKey
|
identityKey
|
||||||
);
|
);
|
||||||
|
|
||||||
const address = `${identifier}.${deviceId}`;
|
const address = new QualifiedAddress(
|
||||||
|
ourUuid,
|
||||||
|
new Address(theirUuid, deviceId)
|
||||||
|
);
|
||||||
await window.textsecure.storage.protocol
|
await window.textsecure.storage.protocol
|
||||||
.enqueueSessionJob(address, () =>
|
.enqueueSessionJob(address, () =>
|
||||||
processPreKeyBundle(
|
processPreKeyBundle(
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { WebAPICredentials } from '../Types.d';
|
||||||
|
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import { StorageInterface } from '../../types/Storage.d';
|
import { StorageInterface } from '../../types/Storage.d';
|
||||||
|
import { UUID } from '../../types/UUID';
|
||||||
|
|
||||||
import Helpers from '../Helpers';
|
import Helpers from '../Helpers';
|
||||||
|
|
||||||
|
@ -56,10 +57,16 @@ export class User {
|
||||||
return Helpers.unencodeNumber(numberId)[0];
|
return Helpers.unencodeNumber(numberId)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUuid(): string | undefined {
|
public getUuid(): UUID | undefined {
|
||||||
const uuid = this.storage.get('uuid_id');
|
const uuid = this.storage.get('uuid_id');
|
||||||
if (uuid === undefined) return undefined;
|
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 {
|
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 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`.
|
// This should be in sync with `STORAGE_UI_KEYS` in `ts/types/StorageUIKeys.ts`.
|
||||||
export type StorageAccessType = {
|
export type StorageAccessType = {
|
||||||
'always-relay-calls': boolean;
|
'always-relay-calls': boolean;
|
||||||
|
@ -55,7 +63,7 @@ export type StorageAccessType = {
|
||||||
customColors: CustomColorsItemType;
|
customColors: CustomColorsItemType;
|
||||||
device_name: string;
|
device_name: string;
|
||||||
hasRegisterSupportForUnauthenticatedDelivery: boolean;
|
hasRegisterSupportForUnauthenticatedDelivery: boolean;
|
||||||
identityKey: KeyPairType;
|
identityKeyMap: IdentityKeyMap;
|
||||||
lastHeartbeat: number;
|
lastHeartbeat: number;
|
||||||
lastStartup: number;
|
lastStartup: number;
|
||||||
lastAttemptedToRefreshProfilesAt: number;
|
lastAttemptedToRefreshProfilesAt: number;
|
||||||
|
@ -64,7 +72,7 @@ export type StorageAccessType = {
|
||||||
password: string;
|
password: string;
|
||||||
profileKey: ArrayBuffer;
|
profileKey: ArrayBuffer;
|
||||||
regionCode: string;
|
regionCode: string;
|
||||||
registrationId: number;
|
registrationIdMap: Record<string, number>;
|
||||||
remoteBuildExpiration: number;
|
remoteBuildExpiration: number;
|
||||||
sessionResets: SessionResetsType;
|
sessionResets: SessionResetsType;
|
||||||
showStickerPickerHint: boolean;
|
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 { ProfileKeyCredentialRequestContext } from 'zkgroup';
|
||||||
import { SEALED_SENDER } from '../types/SealedSender';
|
import { SEALED_SENDER } from '../types/SealedSender';
|
||||||
|
import { Address } from '../types/Address';
|
||||||
|
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||||
|
import { UUID } from '../types/UUID';
|
||||||
import {
|
import {
|
||||||
base64ToArrayBuffer,
|
base64ToArrayBuffer,
|
||||||
stringFromBytes,
|
stringFromBytes,
|
||||||
|
@ -54,6 +57,7 @@ export async function getProfile(
|
||||||
const uuid = c.get('uuid')!;
|
const uuid = c.get('uuid')!;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const identifier = c.getSendTarget()!;
|
const identifier = c.getSendTarget()!;
|
||||||
|
const targetUuid = UUID.checkedLookup(identifier);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const profileKeyVersionHex = c.get('profileKeyVersion')!;
|
const profileKeyVersionHex = c.get('profileKeyVersion')!;
|
||||||
const existingProfileKeyCredential = c.get('profileKeyCredential');
|
const existingProfileKeyCredential = c.get('profileKeyCredential');
|
||||||
|
@ -115,15 +119,16 @@ export async function getProfile(
|
||||||
|
|
||||||
const identityKey = base64ToArrayBuffer(profile.identityKey);
|
const identityKey = base64ToArrayBuffer(profile.identityKey);
|
||||||
const changed = await window.textsecure.storage.protocol.saveIdentity(
|
const changed = await window.textsecure.storage.protocol.saveIdentity(
|
||||||
`${identifier}.1`,
|
new Address(targetUuid, 1),
|
||||||
identityKey,
|
identityKey,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
if (changed) {
|
if (changed) {
|
||||||
// save identity will close all sessions except for .1, so we
|
// save identity will close all sessions except for .1, so we
|
||||||
// must close that one manually.
|
// must close that one manually.
|
||||||
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
await window.textsecure.storage.protocol.archiveSession(
|
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 { isOlderThan } from './timestamp';
|
||||||
import { parseIntOrThrow } from './parseIntOrThrow';
|
import { parseIntOrThrow } from './parseIntOrThrow';
|
||||||
import * as RemoteConfig from '../RemoteConfig';
|
import * as RemoteConfig from '../RemoteConfig';
|
||||||
|
import { Address } from '../types/Address';
|
||||||
|
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||||
|
|
||||||
import { ConversationModel } from '../models/conversations';
|
import { ConversationModel } from '../models/conversations';
|
||||||
import {
|
import {
|
||||||
|
@ -184,7 +186,11 @@ async function archiveSessionOnMatch({
|
||||||
return;
|
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);
|
const session = await window.textsecure.storage.protocol.loadSession(address);
|
||||||
|
|
||||||
if (session && session.currentRatchetKeyMatches(ratchetKey)) {
|
if (session && session.currentRatchetKeyMatches(ratchetKey)) {
|
||||||
|
@ -500,9 +506,10 @@ function scheduleSessionReset(senderUuid: string, senderDevice: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
lightSessionResetQueue.add(() => {
|
lightSessionResetQueue.add(() => {
|
||||||
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
|
|
||||||
window.textsecure.storage.protocol.lightSessionReset(
|
window.textsecure.storage.protocol.lightSessionReset(
|
||||||
senderUuid,
|
new QualifiedAddress(ourUuid, Address.create(senderUuid, senderDevice))
|
||||||
senderDevice
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import { PublicKey, Fingerprint } from '@signalapp/signal-client';
|
import { PublicKey, Fingerprint } from '@signalapp/signal-client';
|
||||||
import { ConversationType } from '../state/ducks/conversations';
|
import { ConversationType } from '../state/ducks/conversations';
|
||||||
|
import { UUID } from '../types/UUID';
|
||||||
|
|
||||||
import { assert } from './assert';
|
import { assert } from './assert';
|
||||||
|
|
||||||
|
@ -33,16 +34,18 @@ export async function generateSecurityNumber(
|
||||||
export async function generateSecurityNumberBlock(
|
export async function generateSecurityNumberBlock(
|
||||||
contact: ConversationType
|
contact: ConversationType
|
||||||
): Promise<Array<string>> {
|
): Promise<Array<string>> {
|
||||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
const { storage } = window.textsecure;
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
const ourNumber = storage.user.getNumber();
|
||||||
|
const ourUuid = storage.user.getCheckedUuid();
|
||||||
|
|
||||||
const us = window.textsecure.storage.protocol.getIdentityRecord(
|
const us = storage.protocol.getIdentityRecord(ourUuid);
|
||||||
ourUuid || ourNumber || ''
|
|
||||||
);
|
|
||||||
const ourKey = us ? us.publicKey : null;
|
const ourKey = us ? us.publicKey : null;
|
||||||
|
|
||||||
const them = window.textsecure.storage.protocol.getIdentityRecord(contact.id);
|
const theirUuid = UUID.lookup(contact.id);
|
||||||
const theirKey = them ? them.publicKey : null;
|
const them = theirUuid
|
||||||
|
? await storage.protocol.getOrMigrateIdentityRecord(theirUuid)
|
||||||
|
: undefined;
|
||||||
|
const theirKey = them?.publicKey;
|
||||||
|
|
||||||
if (!ourKey) {
|
if (!ourKey) {
|
||||||
throw new Error('Could not load our key');
|
throw new Error('Could not load our key');
|
||||||
|
|
|
@ -20,6 +20,9 @@ import {
|
||||||
SenderCertificateMode,
|
SenderCertificateMode,
|
||||||
SendLogCallbackType,
|
SendLogCallbackType,
|
||||||
} from '../textsecure/OutgoingMessage';
|
} from '../textsecure/OutgoingMessage';
|
||||||
|
import { Address } from '../types/Address';
|
||||||
|
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||||
|
import { UUID } from '../types/UUID';
|
||||||
import { isEnabled } from '../RemoteConfig';
|
import { isEnabled } from '../RemoteConfig';
|
||||||
|
|
||||||
import { isOlderThan } from './timestamp';
|
import { isOlderThan } from './timestamp';
|
||||||
|
@ -286,10 +289,14 @@ export async function sendToGroupViaSenderKey(options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Fetch all devices we believe we'll be sending to
|
// 2. Fetch all devices we believe we'll be sending to
|
||||||
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const {
|
const {
|
||||||
devices: currentDevices,
|
devices: currentDevices,
|
||||||
emptyIdentifiers,
|
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
|
// 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
|
// believe that any have signal accounts, fetch their prekey bundle and start
|
||||||
|
@ -669,7 +676,13 @@ async function markIdentifierUnregistered(identifier: string) {
|
||||||
conversation.setUnregistered();
|
conversation.setUnregistered();
|
||||||
window.Signal.Data.updateConversation(conversation.attributes);
|
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) {
|
function isIdentifierRegistered(identifier: string) {
|
||||||
|
@ -695,10 +708,13 @@ async function handle409Response(logId: string, error: Error) {
|
||||||
|
|
||||||
// Archive sessions with devices that have been removed
|
// Archive sessions with devices that have been removed
|
||||||
if (devices.extraDevices && devices.extraDevices.length > 0) {
|
if (devices.extraDevices && devices.extraDevices.length > 0) {
|
||||||
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
|
|
||||||
await _waitForAll({
|
await _waitForAll({
|
||||||
tasks: devices.extraDevices.map(deviceId => async () => {
|
tasks: devices.extraDevices.map(deviceId => async () => {
|
||||||
const address = `${uuid}.${deviceId}`;
|
await window.textsecure.storage.protocol.archiveSession(
|
||||||
await window.textsecure.storage.protocol.archiveSession(address);
|
new QualifiedAddress(ourUuid, Address.create(uuid, deviceId))
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -727,11 +743,14 @@ async function handle410Response(
|
||||||
tasks: parsed.data.map(item => async () => {
|
tasks: parsed.data.map(item => async () => {
|
||||||
const { uuid, devices } = item;
|
const { uuid, devices } = item;
|
||||||
if (devices.staleDevices && devices.staleDevices.length > 0) {
|
if (devices.staleDevices && devices.staleDevices.length > 0) {
|
||||||
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
|
|
||||||
// First, archive our existing sessions with these devices
|
// First, archive our existing sessions with these devices
|
||||||
await _waitForAll({
|
await _waitForAll({
|
||||||
tasks: devices.staleDevices.map(deviceId => async () => {
|
tasks: devices.staleDevices.map(deviceId => async () => {
|
||||||
const address = `${uuid}.${deviceId}`;
|
await window.textsecure.storage.protocol.archiveSession(
|
||||||
await window.textsecure.storage.protocol.archiveSession(address);
|
new QualifiedAddress(ourUuid, Address.create(uuid, deviceId))
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -822,24 +841,24 @@ async function encryptForSenderKey({
|
||||||
distributionId: string;
|
distributionId: string;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
}): Promise<Buffer> {
|
}): Promise<Buffer> {
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||||
if (!ourUuid || !ourDeviceId) {
|
if (!ourDeviceId) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'encryptForSenderKey: Unable to fetch our uuid or deviceId'
|
'encryptForSenderKey: Unable to fetch our uuid or deviceId'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sender = ProtocolAddress.new(
|
const sender = ProtocolAddress.new(
|
||||||
ourUuid,
|
ourUuid.toString(),
|
||||||
parseIntOrThrow(ourDeviceId, 'encryptForSenderKey, ourDeviceId')
|
parseIntOrThrow(ourDeviceId, 'encryptForSenderKey, ourDeviceId')
|
||||||
);
|
);
|
||||||
const ourAddress = getOurAddress();
|
const ourAddress = getOurAddress();
|
||||||
const senderKeyStore = new SenderKeys();
|
const senderKeyStore = new SenderKeys({ ourUuid });
|
||||||
const message = Buffer.from(padMessage(new FIXMEU8(contentMessage)));
|
const message = Buffer.from(padMessage(new FIXMEU8(contentMessage)));
|
||||||
|
|
||||||
const ciphertextMessage = await window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
const ciphertextMessage = await window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
||||||
ourAddress,
|
new QualifiedAddress(ourUuid, ourAddress),
|
||||||
() => groupEncrypt(sender, distributionId, senderKeyStore, message)
|
() => groupEncrypt(sender, distributionId, senderKeyStore, message)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -874,9 +893,14 @@ async function encryptForSenderKey({
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
})
|
})
|
||||||
.map(device => ProtocolAddress.new(device.identifier, device.id));
|
.map(device => {
|
||||||
const identityKeyStore = new IdentityKeys();
|
return ProtocolAddress.new(
|
||||||
const sessionStore = new Sessions();
|
UUID.checkedLookup(device.identifier).toString(),
|
||||||
|
device.id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const identityKeyStore = new IdentityKeys({ ourUuid });
|
||||||
|
const sessionStore = new Sessions({ ourUuid });
|
||||||
return sealedSenderMultiRecipientEncrypt(
|
return sealedSenderMultiRecipientEncrypt(
|
||||||
content,
|
content,
|
||||||
recipients,
|
recipients,
|
||||||
|
@ -998,13 +1022,13 @@ export function _analyzeSenderKeyDevices(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOurAddress(): string {
|
function getOurAddress(): Address {
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||||
if (!ourUuid || !ourDeviceId) {
|
if (!ourDeviceId) {
|
||||||
throw new Error('getOurAddress: Unable to fetch our uuid or deviceId');
|
throw new Error('getOurAddress: Unable to fetch our deviceId');
|
||||||
}
|
}
|
||||||
return `${ourUuid}.${ourDeviceId}`;
|
return new Address(ourUuid, ourDeviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetSenderKey(conversation: ConversationModel): Promise<void> {
|
async function resetSenderKey(conversation: ConversationModel): Promise<void> {
|
||||||
|
@ -1023,7 +1047,7 @@ async function resetSenderKey(conversation: ConversationModel): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { distributionId } = senderKeyInfo;
|
const { distributionId } = senderKeyInfo;
|
||||||
const address = getOurAddress();
|
const ourAddress = getOurAddress();
|
||||||
|
|
||||||
// Note: We preserve existing distributionId to minimize space for sender key storage
|
// Note: We preserve existing distributionId to minimize space for sender key storage
|
||||||
conversation.set({
|
conversation.set({
|
||||||
|
@ -1035,8 +1059,9 @@ async function resetSenderKey(conversation: ConversationModel): Promise<void> {
|
||||||
});
|
});
|
||||||
window.Signal.Data.updateConversation(conversation.attributes);
|
window.Signal.Data.updateConversation(conversation.attributes);
|
||||||
|
|
||||||
|
const ourUuid = window.storage.user.getCheckedUuid();
|
||||||
await window.textsecure.storage.protocol.removeSenderKey(
|
await window.textsecure.storage.protocol.removeSenderKey(
|
||||||
address,
|
new QualifiedAddress(ourUuid, ourAddress),
|
||||||
distributionId
|
distributionId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ export function isDirectConversation(
|
||||||
export function isMe(conversationAttrs: ConversationAttributesType): boolean {
|
export function isMe(conversationAttrs: ConversationAttributesType): boolean {
|
||||||
const { e164, uuid } = conversationAttrs;
|
const { e164, uuid } = conversationAttrs;
|
||||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
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));
|
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 { isValidGuid } from './util/isValidGuid';
|
||||||
import { StateType } from './state/reducer';
|
import { StateType } from './state/reducer';
|
||||||
import { SystemTraySetting } from './types/SystemTraySetting';
|
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 { CI } from './CI';
|
||||||
import { IPCEventsType } from './util/createIPCEvents';
|
import { IPCEventsType } from './util/createIPCEvents';
|
||||||
import { ConversationView } from './views/conversation_view';
|
import { ConversationView } from './views/conversation_view';
|
||||||
|
@ -396,6 +399,9 @@ declare global {
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
VisualAttachment: any;
|
VisualAttachment: any;
|
||||||
|
UUID: typeof UUID;
|
||||||
|
Address: typeof Address;
|
||||||
|
QualifiedAddress: typeof QualifiedAddress;
|
||||||
};
|
};
|
||||||
Util: typeof Util;
|
Util: typeof Util;
|
||||||
GroupChange: {
|
GroupChange: {
|
||||||
|
@ -595,7 +601,6 @@ export type WhisperType = {
|
||||||
MessageCollection: typeof MessageModelCollectionType;
|
MessageCollection: typeof MessageModelCollectionType;
|
||||||
|
|
||||||
GroupMemberConversation: WhatIsThis;
|
GroupMemberConversation: WhatIsThis;
|
||||||
KeyChangeListener: WhatIsThis;
|
|
||||||
RotateSignedPreKeyListener: WhatIsThis;
|
RotateSignedPreKeyListener: WhatIsThis;
|
||||||
WallClockListener: WhatIsThis;
|
WallClockListener: WhatIsThis;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue