Receive support for Sender Key
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
parent
e5f9c0db28
commit
e6bab06510
10
package.json
10
package.json
|
@ -68,6 +68,7 @@
|
||||||
"fs-xattr": "0.3.0"
|
"fs-xattr": "0.3.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@signalapp/signal-client": "0.5.1",
|
||||||
"@sindresorhus/is": "0.8.0",
|
"@sindresorhus/is": "0.8.0",
|
||||||
"@types/pino": "6.3.6",
|
"@types/pino": "6.3.6",
|
||||||
"@types/pino-multi-stream": "5.1.0",
|
"@types/pino-multi-stream": "5.1.0",
|
||||||
|
@ -102,7 +103,6 @@
|
||||||
"intl-tel-input": "12.1.15",
|
"intl-tel-input": "12.1.15",
|
||||||
"jquery": "3.5.0",
|
"jquery": "3.5.0",
|
||||||
"js-yaml": "3.13.1",
|
"js-yaml": "3.13.1",
|
||||||
"libsignal-client": "https://github.com/signalapp/libsignal-client-node.git#afa8f40eaa218b7fff94278d9b2ab2e13a2ee04b",
|
|
||||||
"linkify-it": "2.2.0",
|
"linkify-it": "2.2.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"lru-cache": "6.0.0",
|
"lru-cache": "6.0.0",
|
||||||
|
@ -294,7 +294,7 @@
|
||||||
"asarUnpack": [
|
"asarUnpack": [
|
||||||
"**/*.node",
|
"**/*.node",
|
||||||
"node_modules/zkgroup/libzkgroup.*",
|
"node_modules/zkgroup/libzkgroup.*",
|
||||||
"node_modules/libsignal-client/build/*.node"
|
"node_modules/@signalapp/signal-client/build/*.node"
|
||||||
],
|
],
|
||||||
"artifactName": "${name}-mac-${version}.${ext}",
|
"artifactName": "${name}-mac-${version}.${ext}",
|
||||||
"category": "public.app-category.social-networking",
|
"category": "public.app-category.social-networking",
|
||||||
|
@ -320,7 +320,7 @@
|
||||||
"node_modules/spellchecker/vendor/hunspell_dictionaries",
|
"node_modules/spellchecker/vendor/hunspell_dictionaries",
|
||||||
"node_modules/sharp",
|
"node_modules/sharp",
|
||||||
"node_modules/zkgroup/libzkgroup.*",
|
"node_modules/zkgroup/libzkgroup.*",
|
||||||
"node_modules/libsignal-client/build/*.node"
|
"node_modules/@signalapp/signal-client/build/*.node"
|
||||||
],
|
],
|
||||||
"artifactName": "${name}-win-${version}.${ext}",
|
"artifactName": "${name}-win-${version}.${ext}",
|
||||||
"certificateSubjectName": "Signal (Quiet Riddle Ventures, LLC)",
|
"certificateSubjectName": "Signal (Quiet Riddle Ventures, LLC)",
|
||||||
|
@ -350,7 +350,7 @@
|
||||||
"node_modules/spellchecker/vendor/hunspell_dictionaries",
|
"node_modules/spellchecker/vendor/hunspell_dictionaries",
|
||||||
"node_modules/sharp",
|
"node_modules/sharp",
|
||||||
"node_modules/zkgroup/libzkgroup.*",
|
"node_modules/zkgroup/libzkgroup.*",
|
||||||
"node_modules/libsignal-client/build/*.node"
|
"node_modules/@signalapp/signal-client/build/*.node"
|
||||||
],
|
],
|
||||||
"target": [
|
"target": [
|
||||||
"deb"
|
"deb"
|
||||||
|
@ -438,7 +438,7 @@
|
||||||
"!node_modules/better-sqlite3/deps/*",
|
"!node_modules/better-sqlite3/deps/*",
|
||||||
"!node_modules/better-sqlite3/src/*",
|
"!node_modules/better-sqlite3/src/*",
|
||||||
"node_modules/better-sqlite3/build/Release/better_sqlite3.node",
|
"node_modules/better-sqlite3/build/Release/better_sqlite3.node",
|
||||||
"node_modules/libsignal-client/build/*${platform}*.node",
|
"node_modules/@signalapp/signal-client/build/*${platform}*.node",
|
||||||
"node_modules/ringrtc/build/${platform}/**",
|
"node_modules/ringrtc/build/${platform}/**",
|
||||||
"!**/node_modules/ffi-napi/deps",
|
"!**/node_modules/ffi-napi/deps",
|
||||||
"!**/node_modules/react-dom/*/*.development.js",
|
"!**/node_modules/react-dom/*/*.development.js",
|
||||||
|
|
|
@ -12,6 +12,7 @@ message Envelope {
|
||||||
PREKEY_BUNDLE = 3;
|
PREKEY_BUNDLE = 3;
|
||||||
RECEIPT = 5;
|
RECEIPT = 5;
|
||||||
UNIDENTIFIED_SENDER = 6;
|
UNIDENTIFIED_SENDER = 6;
|
||||||
|
SENDERKEY = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
|
@ -27,12 +28,13 @@ message Envelope {
|
||||||
}
|
}
|
||||||
|
|
||||||
message Content {
|
message Content {
|
||||||
optional DataMessage dataMessage = 1;
|
optional DataMessage dataMessage = 1;
|
||||||
optional SyncMessage syncMessage = 2;
|
optional SyncMessage syncMessage = 2;
|
||||||
optional CallingMessage callingMessage = 3;
|
optional CallingMessage callingMessage = 3;
|
||||||
optional NullMessage nullMessage = 4;
|
optional NullMessage nullMessage = 4;
|
||||||
optional ReceiptMessage receiptMessage = 5;
|
optional ReceiptMessage receiptMessage = 5;
|
||||||
optional TypingMessage typingMessage = 6;
|
optional TypingMessage typingMessage = 6;
|
||||||
|
optional bytes senderKeyDistributionMessage = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything in CallingMessage must be kept in sync with RingRTC (ringrtc-node).
|
// Everything in CallingMessage must be kept in sync with RingRTC (ringrtc-node).
|
||||||
|
@ -362,7 +364,7 @@ message SyncMessage {
|
||||||
optional bool readReceipts = 1;
|
optional bool readReceipts = 1;
|
||||||
optional bool unidentifiedDeliveryIndicators = 2;
|
optional bool unidentifiedDeliveryIndicators = 2;
|
||||||
optional bool typingIndicators = 3;
|
optional bool typingIndicators = 3;
|
||||||
// 4 is reserved
|
reserved 4;
|
||||||
optional uint32 provisioningVersion = 5;
|
optional uint32 provisioningVersion = 5;
|
||||||
optional bool linkPreviews = 6;
|
optional bool linkPreviews = 6;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,12 @@ message ServerCertificate {
|
||||||
|
|
||||||
message SenderCertificate {
|
message SenderCertificate {
|
||||||
message Certificate {
|
message Certificate {
|
||||||
optional string sender = 1;
|
optional string senderE164 = 1;
|
||||||
optional string senderUuid = 6;
|
optional string senderUuid = 6;
|
||||||
optional uint32 senderDevice = 2;
|
optional uint32 senderDevice = 2;
|
||||||
optional fixed64 expires = 3;
|
optional fixed64 expires = 3;
|
||||||
optional bytes identityKey = 4;
|
optional bytes identityKey = 4;
|
||||||
optional ServerCertificate signer = 5;
|
optional ServerCertificate signer = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional bytes certificate = 1;
|
optional bytes certificate = 1;
|
||||||
|
@ -31,16 +31,34 @@ message UnidentifiedSenderMessage {
|
||||||
|
|
||||||
message Message {
|
message Message {
|
||||||
enum Type {
|
enum Type {
|
||||||
PREKEY_MESSAGE = 1;
|
PREKEY_MESSAGE = 1;
|
||||||
MESSAGE = 2;
|
MESSAGE = 2;
|
||||||
|
// Further cases should line up with Envelope.Type, even though old cases don't.
|
||||||
|
|
||||||
|
// Our parser does not handle reserved in enums: DESKTOP-1569
|
||||||
|
// reserved 3 to 6;
|
||||||
|
|
||||||
|
SENDERKEY_MESSAGE = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ContentHint {
|
||||||
|
// Commented out here, even though it is correct syntax. Our parser cannot handle it.
|
||||||
|
|
||||||
|
// Our parser does not handle reserved in enums: DESKTOP-1569
|
||||||
|
// reserved 0; // A content hint of "default" should never be encoded.
|
||||||
|
|
||||||
|
SUPPLEMENTARY = 1;
|
||||||
|
RETRY = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
optional SenderCertificate senderCertificate = 2;
|
optional SenderCertificate senderCertificate = 2;
|
||||||
optional bytes content = 3;
|
optional bytes content = 3;
|
||||||
|
optional ContentHint contentHint = 4;
|
||||||
|
optional bytes groupId = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional bytes ephemeralPublic = 1;
|
optional bytes ephemeralPublic = 1;
|
||||||
optional bytes encryptedStatic = 2;
|
optional bytes encryptedStatic = 2;
|
||||||
optional bytes encryptedMessage = 3;
|
optional bytes encryptedMessage = 3;
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ const {
|
||||||
const SKIPPED_DEPENDENCIES = new Set([
|
const SKIPPED_DEPENDENCIES = new Set([
|
||||||
'ringrtc',
|
'ringrtc',
|
||||||
'zkgroup',
|
'zkgroup',
|
||||||
'libsignal-client',
|
'@signalapp/signal-client',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const rootDir = join(__dirname, '..');
|
const rootDir = join(__dirname, '..');
|
||||||
|
|
|
@ -9,7 +9,7 @@ const { readFile } = require('fs');
|
||||||
const config = require('url').parse(window.location.toString(), true).query;
|
const config = require('url').parse(window.location.toString(), true).query;
|
||||||
const { noop, uniqBy } = require('lodash');
|
const { noop, uniqBy } = require('lodash');
|
||||||
const pMap = require('p-map');
|
const pMap = require('p-map');
|
||||||
const client = require('libsignal-client');
|
const client = require('@signalapp/signal-client');
|
||||||
const { deriveStickerPackKey } = require('../ts/Crypto');
|
const { deriveStickerPackKey } = require('../ts/Crypto');
|
||||||
const {
|
const {
|
||||||
getEnvironment,
|
getEnvironment,
|
||||||
|
|
|
@ -23,7 +23,7 @@ describe('Crypto', () => {
|
||||||
const result = window.Signal.Crypto.deriveSecrets(input, salt, info);
|
const result = window.Signal.Crypto.deriveSecrets(input, salt, info);
|
||||||
assert.lengthOf(result, 3);
|
assert.lengthOf(result, 3);
|
||||||
result.forEach(part => {
|
result.forEach(part => {
|
||||||
// This is a smoke test; HKDF is tested as part of libsignal-client.
|
// This is a smoke test; HKDF is tested as part of @signalapp/signal-client.
|
||||||
assert.instanceOf(part, ArrayBuffer);
|
assert.instanceOf(part, ArrayBuffer);
|
||||||
assert.strictEqual(part.byteLength, 32);
|
assert.strictEqual(part.byteLength, 32);
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import pProps from 'p-props';
|
import pProps from 'p-props';
|
||||||
import { chunk } from 'lodash';
|
import { chunk } from 'lodash';
|
||||||
import { HKDF } from 'libsignal-client';
|
import { HKDF } from '@signalapp/signal-client';
|
||||||
import { calculateAgreement, generateKeyPair } from './Curve';
|
import { calculateAgreement, generateKeyPair } from './Curve';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as client from 'libsignal-client';
|
import * as client from '@signalapp/signal-client';
|
||||||
|
|
||||||
import { constantTimeEqual, typedArrayToArrayBuffer } from './Crypto';
|
import { constantTimeEqual, typedArrayToArrayBuffer } from './Crypto';
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -8,17 +8,20 @@ import { isNumber } from 'lodash';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Direction,
|
Direction,
|
||||||
ProtocolAddress,
|
|
||||||
SessionStore,
|
|
||||||
SessionRecord,
|
|
||||||
IdentityKeyStore,
|
IdentityKeyStore,
|
||||||
PreKeyRecord,
|
PreKeyRecord,
|
||||||
PreKeyStore,
|
PreKeyStore,
|
||||||
PrivateKey,
|
PrivateKey,
|
||||||
|
ProtocolAddress,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
SignedPreKeyStore,
|
SenderKeyRecord,
|
||||||
|
SenderKeyStore,
|
||||||
|
SessionRecord,
|
||||||
|
SessionStore,
|
||||||
SignedPreKeyRecord,
|
SignedPreKeyRecord,
|
||||||
} from 'libsignal-client';
|
SignedPreKeyStore,
|
||||||
|
Uuid,
|
||||||
|
} from '@signalapp/signal-client';
|
||||||
import { freezePreKey, freezeSignedPreKey } from './SignalProtocolStore';
|
import { freezePreKey, freezeSignedPreKey } from './SignalProtocolStore';
|
||||||
|
|
||||||
import { typedArrayToArrayBuffer } from './Crypto';
|
import { typedArrayToArrayBuffer } from './Crypto';
|
||||||
|
@ -131,6 +134,36 @@ export class PreKeys extends PreKeyStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SenderKeys extends SenderKeyStore {
|
||||||
|
async saveSenderKey(
|
||||||
|
sender: ProtocolAddress,
|
||||||
|
distributionId: Uuid,
|
||||||
|
record: SenderKeyRecord
|
||||||
|
): Promise<void> {
|
||||||
|
const encodedAddress = encodedNameFromAddress(sender);
|
||||||
|
|
||||||
|
await window.textsecure.storage.protocol.saveSenderKey(
|
||||||
|
encodedAddress,
|
||||||
|
distributionId,
|
||||||
|
record
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSenderKey(
|
||||||
|
sender: ProtocolAddress,
|
||||||
|
distributionId: Uuid
|
||||||
|
): Promise<SenderKeyRecord | null> {
|
||||||
|
const encodedAddress = encodedNameFromAddress(sender);
|
||||||
|
|
||||||
|
const senderKey = await window.textsecure.storage.protocol.getSenderKey(
|
||||||
|
encodedAddress,
|
||||||
|
distributionId
|
||||||
|
);
|
||||||
|
|
||||||
|
return senderKey || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class SignedPreKeys extends SignedPreKeyStore {
|
export class SignedPreKeys extends SignedPreKeyStore {
|
||||||
async saveSignedPreKey(
|
async saveSignedPreKey(
|
||||||
id: number,
|
id: number,
|
||||||
|
|
|
@ -8,13 +8,14 @@ import { isNumber } from 'lodash';
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SessionRecord,
|
Direction,
|
||||||
PreKeyRecord,
|
PreKeyRecord,
|
||||||
PrivateKey,
|
PrivateKey,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
|
SenderKeyRecord,
|
||||||
|
SessionRecord,
|
||||||
SignedPreKeyRecord,
|
SignedPreKeyRecord,
|
||||||
Direction,
|
} from '@signalapp/signal-client';
|
||||||
} from 'libsignal-client';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
constantTimeEqual,
|
constantTimeEqual,
|
||||||
|
@ -30,6 +31,7 @@ import {
|
||||||
import {
|
import {
|
||||||
KeyPairType,
|
KeyPairType,
|
||||||
IdentityKeyType,
|
IdentityKeyType,
|
||||||
|
SenderKeyType,
|
||||||
SessionType,
|
SessionType,
|
||||||
SignedPreKeyType,
|
SignedPreKeyType,
|
||||||
OuterSignedPrekeyType,
|
OuterSignedPrekeyType,
|
||||||
|
@ -90,8 +92,8 @@ async function normalizeEncodedAddress(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type HasIdType = {
|
type HasIdType<T> = {
|
||||||
id: string | number;
|
id: T;
|
||||||
};
|
};
|
||||||
type CacheEntryType<DBType, HydratedType> =
|
type CacheEntryType<DBType, HydratedType> =
|
||||||
| {
|
| {
|
||||||
|
@ -100,24 +102,22 @@ type CacheEntryType<DBType, HydratedType> =
|
||||||
}
|
}
|
||||||
| { hydrated: true; fromDB: DBType; item: HydratedType };
|
| { hydrated: true; fromDB: DBType; item: HydratedType };
|
||||||
|
|
||||||
async function _fillCaches<T extends HasIdType, HydratedType>(
|
async function _fillCaches<ID, T extends HasIdType<ID>, HydratedType>(
|
||||||
object: SignalProtocolStore,
|
object: SignalProtocolStore,
|
||||||
field: keyof SignalProtocolStore,
|
field: keyof SignalProtocolStore,
|
||||||
itemsPromise: Promise<Array<T>>
|
itemsPromise: Promise<Array<T>>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const items = await itemsPromise;
|
const items = await itemsPromise;
|
||||||
|
|
||||||
const cache: Record<string, CacheEntryType<T, HydratedType>> = Object.create(
|
const cache = new Map<ID, CacheEntryType<T, HydratedType>>();
|
||||||
null
|
|
||||||
);
|
|
||||||
for (let i = 0, max = items.length; i < max; i += 1) {
|
for (let i = 0, max = items.length; i < max; i += 1) {
|
||||||
const fromDB = items[i];
|
const fromDB = items[i];
|
||||||
const { id } = fromDB;
|
const { id } = fromDB;
|
||||||
|
|
||||||
cache[id] = {
|
cache.set(id, {
|
||||||
fromDB,
|
fromDB,
|
||||||
hydrated: false,
|
hydrated: false,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.log.info(`SignalProtocolStore: Finished caching ${field} data`);
|
window.log.info(`SignalProtocolStore: Finished caching ${field} data`);
|
||||||
|
@ -193,17 +193,21 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
|
|
||||||
ourRegistrationId?: number;
|
ourRegistrationId?: number;
|
||||||
|
|
||||||
identityKeys?: Record<string, CacheEntryType<IdentityKeyType, PublicKey>>;
|
identityKeys?: Map<string, CacheEntryType<IdentityKeyType, PublicKey>>;
|
||||||
|
|
||||||
sessions?: Record<string, CacheEntryType<SessionType, SessionRecord>>;
|
senderKeys?: Map<string, CacheEntryType<SenderKeyType, SenderKeyRecord>>;
|
||||||
|
|
||||||
preKeys?: Record<string, CacheEntryType<PreKeyType, PreKeyRecord>>;
|
sessions?: Map<string, CacheEntryType<SessionType, SessionRecord>>;
|
||||||
|
|
||||||
signedPreKeys?: Record<
|
preKeys?: Map<number, CacheEntryType<PreKeyType, PreKeyRecord>>;
|
||||||
string,
|
|
||||||
|
signedPreKeys?: Map<
|
||||||
|
number,
|
||||||
CacheEntryType<SignedPreKeyType, SignedPreKeyRecord>
|
CacheEntryType<SignedPreKeyType, SignedPreKeyRecord>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
senderKeyQueues: Map<string, PQueue> = new Map<string, PQueue>();
|
||||||
|
|
||||||
sessionQueues: Map<string, PQueue> = new Map<string, PQueue>();
|
sessionQueues: Map<string, PQueue> = new Map<string, PQueue>();
|
||||||
|
|
||||||
async hydrateCaches(): Promise<void> {
|
async hydrateCaches(): Promise<void> {
|
||||||
|
@ -216,22 +220,27 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
const item = await window.Signal.Data.getItemById('registrationId');
|
const item = await window.Signal.Data.getItemById('registrationId');
|
||||||
this.ourRegistrationId = item ? item.value : undefined;
|
this.ourRegistrationId = item ? item.value : undefined;
|
||||||
})(),
|
})(),
|
||||||
_fillCaches<IdentityKeyType, PublicKey>(
|
_fillCaches<string, IdentityKeyType, PublicKey>(
|
||||||
this,
|
this,
|
||||||
'identityKeys',
|
'identityKeys',
|
||||||
window.Signal.Data.getAllIdentityKeys()
|
window.Signal.Data.getAllIdentityKeys()
|
||||||
),
|
),
|
||||||
_fillCaches<SessionType, SessionRecord>(
|
_fillCaches<string, SessionType, SessionRecord>(
|
||||||
this,
|
this,
|
||||||
'sessions',
|
'sessions',
|
||||||
window.Signal.Data.getAllSessions()
|
window.Signal.Data.getAllSessions()
|
||||||
),
|
),
|
||||||
_fillCaches<PreKeyType, PreKeyRecord>(
|
_fillCaches<number, PreKeyType, PreKeyRecord>(
|
||||||
this,
|
this,
|
||||||
'preKeys',
|
'preKeys',
|
||||||
window.Signal.Data.getAllPreKeys()
|
window.Signal.Data.getAllPreKeys()
|
||||||
),
|
),
|
||||||
_fillCaches<SignedPreKeyType, SignedPreKeyRecord>(
|
_fillCaches<string, SenderKeyType, SenderKeyRecord>(
|
||||||
|
this,
|
||||||
|
'senderKeys',
|
||||||
|
window.Signal.Data.getAllSenderKeys()
|
||||||
|
),
|
||||||
|
_fillCaches<number, SignedPreKeyType, SignedPreKeyRecord>(
|
||||||
this,
|
this,
|
||||||
'signedPreKeys',
|
'signedPreKeys',
|
||||||
window.Signal.Data.getAllSignedPreKeys()
|
window.Signal.Data.getAllSignedPreKeys()
|
||||||
|
@ -249,12 +258,12 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
|
|
||||||
// PreKeys
|
// PreKeys
|
||||||
|
|
||||||
async loadPreKey(keyId: string | number): Promise<PreKeyRecord | undefined> {
|
async loadPreKey(keyId: number): Promise<PreKeyRecord | undefined> {
|
||||||
if (!this.preKeys) {
|
if (!this.preKeys) {
|
||||||
throw new Error('loadPreKey: this.preKeys not yet cached!');
|
throw new Error('loadPreKey: this.preKeys not yet cached!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = this.preKeys[keyId];
|
const entry = this.preKeys.get(keyId);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
window.log.error('Failed to fetch prekey:', keyId);
|
window.log.error('Failed to fetch prekey:', keyId);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -266,11 +275,11 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = hydratePreKey(entry.fromDB);
|
const item = hydratePreKey(entry.fromDB);
|
||||||
this.preKeys[keyId] = {
|
this.preKeys.set(keyId, {
|
||||||
hydrated: true,
|
hydrated: true,
|
||||||
fromDB: entry.fromDB,
|
fromDB: entry.fromDB,
|
||||||
item,
|
item,
|
||||||
};
|
});
|
||||||
window.log.info('Successfully fetched prekey (cache miss):', keyId);
|
window.log.info('Successfully fetched prekey (cache miss):', keyId);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
@ -279,7 +288,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
if (!this.preKeys) {
|
if (!this.preKeys) {
|
||||||
throw new Error('storePreKey: this.preKeys not yet cached!');
|
throw new Error('storePreKey: this.preKeys not yet cached!');
|
||||||
}
|
}
|
||||||
if (this.preKeys[keyId]) {
|
if (this.preKeys.has(keyId)) {
|
||||||
throw new Error(`storePreKey: prekey ${keyId} already exists!`);
|
throw new Error(`storePreKey: prekey ${keyId} already exists!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,10 +299,10 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
};
|
};
|
||||||
|
|
||||||
await window.Signal.Data.createOrUpdatePreKey(fromDB);
|
await window.Signal.Data.createOrUpdatePreKey(fromDB);
|
||||||
this.preKeys[keyId] = {
|
this.preKeys.set(keyId, {
|
||||||
hydrated: false,
|
hydrated: false,
|
||||||
fromDB,
|
fromDB,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async removePreKey(keyId: number): Promise<void> {
|
async removePreKey(keyId: number): Promise<void> {
|
||||||
|
@ -310,12 +319,14 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this.preKeys[keyId];
|
this.preKeys.delete(keyId);
|
||||||
await window.Signal.Data.removePreKeyById(keyId);
|
await window.Signal.Data.removePreKeyById(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearPreKeyStore(): Promise<void> {
|
async clearPreKeyStore(): Promise<void> {
|
||||||
this.preKeys = Object.create(null);
|
if (this.preKeys) {
|
||||||
|
this.preKeys.clear();
|
||||||
|
}
|
||||||
await window.Signal.Data.removeAllPreKeys();
|
await window.Signal.Data.removeAllPreKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,7 +339,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
throw new Error('loadSignedPreKey: this.signedPreKeys not yet cached!');
|
throw new Error('loadSignedPreKey: this.signedPreKeys not yet cached!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = this.signedPreKeys[keyId];
|
const entry = this.signedPreKeys.get(keyId);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
window.log.error('Failed to fetch signed prekey:', keyId);
|
window.log.error('Failed to fetch signed prekey:', keyId);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -340,11 +351,11 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = hydrateSignedPreKey(entry.fromDB);
|
const item = hydrateSignedPreKey(entry.fromDB);
|
||||||
this.signedPreKeys[keyId] = {
|
this.signedPreKeys.set(keyId, {
|
||||||
hydrated: true,
|
hydrated: true,
|
||||||
item,
|
item,
|
||||||
fromDB: entry.fromDB,
|
fromDB: entry.fromDB,
|
||||||
};
|
});
|
||||||
window.log.info('Successfully fetched signed prekey (cache miss):', keyId);
|
window.log.info('Successfully fetched signed prekey (cache miss):', keyId);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
@ -358,7 +369,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
throw new Error('loadSignedPreKeys takes no arguments');
|
throw new Error('loadSignedPreKeys takes no arguments');
|
||||||
}
|
}
|
||||||
|
|
||||||
const entries = Object.values(this.signedPreKeys);
|
const entries = Array.from(this.signedPreKeys.values());
|
||||||
return entries.map(entry => {
|
return entries.map(entry => {
|
||||||
const preKey = entry.fromDB;
|
const preKey = entry.fromDB;
|
||||||
return {
|
return {
|
||||||
|
@ -391,10 +402,10 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
};
|
};
|
||||||
|
|
||||||
await window.Signal.Data.createOrUpdateSignedPreKey(fromDB);
|
await window.Signal.Data.createOrUpdateSignedPreKey(fromDB);
|
||||||
this.signedPreKeys[keyId] = {
|
this.signedPreKeys.set(keyId, {
|
||||||
hydrated: false,
|
hydrated: false,
|
||||||
fromDB,
|
fromDB,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeSignedPreKey(keyId: number): Promise<void> {
|
async removeSignedPreKey(keyId: number): Promise<void> {
|
||||||
|
@ -402,15 +413,126 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
throw new Error('removeSignedPreKey: this.signedPreKeys not yet cached!');
|
throw new Error('removeSignedPreKey: this.signedPreKeys not yet cached!');
|
||||||
}
|
}
|
||||||
|
|
||||||
delete this.signedPreKeys[keyId];
|
this.signedPreKeys.delete(keyId);
|
||||||
await window.Signal.Data.removeSignedPreKeyById(keyId);
|
await window.Signal.Data.removeSignedPreKeyById(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearSignedPreKeysStore(): Promise<void> {
|
async clearSignedPreKeysStore(): Promise<void> {
|
||||||
this.signedPreKeys = Object.create(null);
|
if (this.signedPreKeys) {
|
||||||
|
this.signedPreKeys.clear();
|
||||||
|
}
|
||||||
await window.Signal.Data.removeAllSignedPreKeys();
|
await window.Signal.Data.removeAllSignedPreKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sender Key Queue
|
||||||
|
|
||||||
|
async enqueueSenderKeyJob<T>(
|
||||||
|
encodedAddress: string,
|
||||||
|
task: () => Promise<T>
|
||||||
|
): Promise<T> {
|
||||||
|
const senderId = await normalizeEncodedAddress(encodedAddress);
|
||||||
|
const queue = this._getSenderKeyQueue(senderId);
|
||||||
|
|
||||||
|
return queue.add<T>(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createSenderKeyQueue(): PQueue {
|
||||||
|
return new PQueue({ concurrency: 1, timeout: 1000 * 60 * 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getSenderKeyQueue(senderId: string): PQueue {
|
||||||
|
const cachedQueue = this.senderKeyQueues.get(senderId);
|
||||||
|
if (cachedQueue) {
|
||||||
|
return cachedQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const freshQueue = this._createSenderKeyQueue();
|
||||||
|
this.senderKeyQueues.set(senderId, freshQueue);
|
||||||
|
return freshQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sender Keys
|
||||||
|
|
||||||
|
private getSenderKeyId(senderKeyId: string, distributionId: string): string {
|
||||||
|
return `${senderKeyId}--${distributionId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveSenderKey(
|
||||||
|
encodedAddress: string,
|
||||||
|
distributionId: string,
|
||||||
|
record: SenderKeyRecord
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.senderKeys) {
|
||||||
|
throw new Error('saveSenderKey: this.senderKeys not yet cached!');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const senderId = await normalizeEncodedAddress(encodedAddress);
|
||||||
|
const id = this.getSenderKeyId(senderId, distributionId);
|
||||||
|
|
||||||
|
const fromDB: SenderKeyType = {
|
||||||
|
id,
|
||||||
|
senderId,
|
||||||
|
distributionId,
|
||||||
|
data: record.serialize(),
|
||||||
|
lastUpdatedDate: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await window.Signal.Data.createOrUpdateSenderKey(fromDB);
|
||||||
|
|
||||||
|
this.senderKeys.set(id, {
|
||||||
|
hydrated: true,
|
||||||
|
fromDB,
|
||||||
|
item: record,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const errorString = error && error.stack ? error.stack : error;
|
||||||
|
window.log.error(
|
||||||
|
`saveSenderKey: failed to save senderKey ${encodedAddress}/${distributionId}: ${errorString}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSenderKey(
|
||||||
|
encodedAddress: string,
|
||||||
|
distributionId: string
|
||||||
|
): Promise<SenderKeyRecord | undefined> {
|
||||||
|
if (!this.senderKeys) {
|
||||||
|
throw new Error('getSenderKey: this.senderKeys not yet cached!');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const senderId = await normalizeEncodedAddress(encodedAddress);
|
||||||
|
const id = this.getSenderKeyId(senderId, distributionId);
|
||||||
|
|
||||||
|
const entry = this.senderKeys.get(id);
|
||||||
|
if (!entry) {
|
||||||
|
window.log.error('Failed to fetch sender key:', id);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.hydrated) {
|
||||||
|
window.log.info('Successfully fetched signed prekey (cache hit):', id);
|
||||||
|
return entry.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = SenderKeyRecord.deserialize(entry.fromDB.data);
|
||||||
|
this.senderKeys.set(id, {
|
||||||
|
hydrated: true,
|
||||||
|
item,
|
||||||
|
fromDB: entry.fromDB,
|
||||||
|
});
|
||||||
|
window.log.info('Successfully fetched signed prekey (cache miss):', id);
|
||||||
|
return item;
|
||||||
|
} catch (error) {
|
||||||
|
const errorString = error && error.stack ? error.stack : error;
|
||||||
|
window.log.error(
|
||||||
|
`getSenderKey: failed to load senderKey ${encodedAddress}/${distributionId}: ${errorString}`
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Session Queue
|
// Session Queue
|
||||||
|
|
||||||
async enqueueSessionJob<T>(
|
async enqueueSessionJob<T>(
|
||||||
|
@ -453,7 +575,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const id = await normalizeEncodedAddress(encodedAddress);
|
const id = await normalizeEncodedAddress(encodedAddress);
|
||||||
const entry = this.sessions[id];
|
const entry = this.sessions.get(id);
|
||||||
|
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -464,11 +586,11 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = await this._maybeMigrateSession(entry.fromDB);
|
const item = await this._maybeMigrateSession(entry.fromDB);
|
||||||
this.sessions[id] = {
|
this.sessions.set(id, {
|
||||||
hydrated: true,
|
hydrated: true,
|
||||||
item,
|
item,
|
||||||
fromDB: entry.fromDB,
|
fromDB: entry.fromDB,
|
||||||
};
|
});
|
||||||
return item;
|
return item;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorString = error && error.stack ? error.stack : error;
|
const errorString = error && error.stack ? error.stack : error;
|
||||||
|
@ -544,11 +666,11 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
};
|
};
|
||||||
|
|
||||||
await window.Signal.Data.createOrUpdateSession(fromDB);
|
await window.Signal.Data.createOrUpdateSession(fromDB);
|
||||||
this.sessions[id] = {
|
this.sessions.set(id, {
|
||||||
hydrated: true,
|
hydrated: true,
|
||||||
fromDB,
|
fromDB,
|
||||||
item: record,
|
item: record,
|
||||||
};
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorString = error && error.stack ? error.stack : error;
|
const errorString = error && error.stack ? error.stack : error;
|
||||||
window.log.error(
|
window.log.error(
|
||||||
|
@ -574,7 +696,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allSessions = Object.values(this.sessions);
|
const allSessions = Array.from(this.sessions.values());
|
||||||
const entries = allSessions.filter(
|
const entries = allSessions.filter(
|
||||||
session => session.fromDB.conversationId === id
|
session => session.fromDB.conversationId === id
|
||||||
);
|
);
|
||||||
|
@ -618,7 +740,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
try {
|
try {
|
||||||
const id = await normalizeEncodedAddress(encodedAddress);
|
const id = await normalizeEncodedAddress(encodedAddress);
|
||||||
await window.Signal.Data.removeSessionById(id);
|
await window.Signal.Data.removeSessionById(id);
|
||||||
delete this.sessions[id];
|
this.sessions.delete(id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
`removeSession: Failed to delete session for ${encodedAddress}`
|
`removeSession: Failed to delete session for ${encodedAddress}`
|
||||||
|
@ -639,12 +761,12 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
|
|
||||||
const id = window.ConversationController.getConversationId(identifier);
|
const id = window.ConversationController.getConversationId(identifier);
|
||||||
|
|
||||||
const entries = Object.values(this.sessions);
|
const entries = Array.from(this.sessions.values());
|
||||||
|
|
||||||
for (let i = 0, max = entries.length; i < max; i += 1) {
|
for (let i = 0, max = entries.length; i < max; i += 1) {
|
||||||
const entry = entries[i];
|
const entry = entries[i];
|
||||||
if (entry.fromDB.conversationId === id) {
|
if (entry.fromDB.conversationId === id) {
|
||||||
delete this.sessions[entry.fromDB.id];
|
this.sessions.delete(entry.fromDB.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -681,7 +803,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
window.log.info(`archiveSession: session for ${encodedAddress}`);
|
window.log.info(`archiveSession: session for ${encodedAddress}`);
|
||||||
|
|
||||||
const id = await normalizeEncodedAddress(encodedAddress);
|
const id = await normalizeEncodedAddress(encodedAddress);
|
||||||
const entry = this.sessions[id];
|
const entry = this.sessions.get(id);
|
||||||
|
|
||||||
await this._archiveSession(entry);
|
await this._archiveSession(entry);
|
||||||
}
|
}
|
||||||
|
@ -700,7 +822,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
const [identifier, deviceId] = window.textsecure.utils.unencodeNumber(id);
|
const [identifier, deviceId] = window.textsecure.utils.unencodeNumber(id);
|
||||||
const deviceIdNumber = parseInt(deviceId, 10);
|
const deviceIdNumber = parseInt(deviceId, 10);
|
||||||
|
|
||||||
const allEntries = Object.values(this.sessions);
|
const allEntries = Array.from(this.sessions.values());
|
||||||
const entries = allEntries.filter(
|
const entries = allEntries.filter(
|
||||||
entry =>
|
entry =>
|
||||||
entry.fromDB.conversationId === identifier &&
|
entry.fromDB.conversationId === identifier &&
|
||||||
|
@ -725,7 +847,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
);
|
);
|
||||||
|
|
||||||
const id = window.ConversationController.getConversationId(identifier);
|
const id = window.ConversationController.getConversationId(identifier);
|
||||||
const allEntries = Object.values(this.sessions);
|
const allEntries = Array.from(this.sessions.values());
|
||||||
const entries = allEntries.filter(
|
const entries = allEntries.filter(
|
||||||
entry => entry.fromDB.conversationId === id
|
entry => entry.fromDB.conversationId === id
|
||||||
);
|
);
|
||||||
|
@ -738,7 +860,9 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async clearSessionStore(): Promise<void> {
|
async clearSessionStore(): Promise<void> {
|
||||||
this.sessions = Object.create(null);
|
if (this.sessions) {
|
||||||
|
this.sessions.clear();
|
||||||
|
}
|
||||||
window.Signal.Data.removeAllSessions();
|
window.Signal.Data.removeAllSessions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -757,7 +881,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = this.identityKeys[id];
|
const entry = this.identityKeys.get(id);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -869,10 +993,10 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
const { id } = data;
|
const { id } = data;
|
||||||
|
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey(data);
|
await window.Signal.Data.createOrUpdateIdentityKey(data);
|
||||||
this.identityKeys[id] = {
|
this.identityKeys.set(id, {
|
||||||
hydrated: false,
|
hydrated: false,
|
||||||
fromDB: data,
|
fromDB: data,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveIdentity(
|
async saveIdentity(
|
||||||
|
@ -1271,7 +1395,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
|
|
||||||
const id = window.ConversationController.getConversationId(identifier);
|
const id = window.ConversationController.getConversationId(identifier);
|
||||||
if (id) {
|
if (id) {
|
||||||
delete this.identityKeys[id];
|
this.identityKeys.delete(id);
|
||||||
await window.Signal.Data.removeIdentityKeyById(id);
|
await window.Signal.Data.removeIdentityKeyById(id);
|
||||||
await this.removeAllSessions(id);
|
await this.removeAllSessions(id);
|
||||||
}
|
}
|
||||||
|
|
53
ts/groups.ts
53
ts/groups.ts
|
@ -23,6 +23,7 @@ import dataInterface from './sql/Client';
|
||||||
import { toWebSafeBase64, fromWebSafeBase64 } from './util/webSafeBase64';
|
import { toWebSafeBase64, fromWebSafeBase64 } from './util/webSafeBase64';
|
||||||
import { assert } from './util/assert';
|
import { assert } from './util/assert';
|
||||||
import { isMoreRecentThan } from './util/timestamp';
|
import { isMoreRecentThan } from './util/timestamp';
|
||||||
|
import { isByteBufferEmpty } from './util/isByteBufferEmpty';
|
||||||
import {
|
import {
|
||||||
ConversationAttributesType,
|
ConversationAttributesType,
|
||||||
GroupV2MemberType,
|
GroupV2MemberType,
|
||||||
|
@ -321,10 +322,10 @@ export function parseGroupLink(
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasData(inviteLinkProto.v1Contents.groupMasterKey)) {
|
if (isByteBufferEmpty(inviteLinkProto.v1Contents.groupMasterKey)) {
|
||||||
throw new Error('v1Contents.groupMasterKey had no data!');
|
throw new Error('v1Contents.groupMasterKey had no data!');
|
||||||
}
|
}
|
||||||
if (!hasData(inviteLinkProto.v1Contents.inviteLinkPassword)) {
|
if (isByteBufferEmpty(inviteLinkProto.v1Contents.inviteLinkPassword)) {
|
||||||
throw new Error('v1Contents.inviteLinkPassword had no data!');
|
throw new Error('v1Contents.inviteLinkPassword had no data!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4673,10 +4674,6 @@ function isValidProfileKey(buffer?: ArrayBuffer): boolean {
|
||||||
return Boolean(buffer && buffer.byteLength === 32);
|
return Boolean(buffer && buffer.byteLength === 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasData(data: ProtoBinaryType): boolean {
|
|
||||||
return data && data.limit > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeTimestamp(
|
function normalizeTimestamp(
|
||||||
timestamp: ProtoBigNumberType
|
timestamp: ProtoBigNumberType
|
||||||
): number | ProtoBigNumberType {
|
): number | ProtoBigNumberType {
|
||||||
|
@ -4703,7 +4700,7 @@ function decryptGroupChange(
|
||||||
): GroupChangeClass.Actions {
|
): GroupChangeClass.Actions {
|
||||||
const clientZkGroupCipher = getClientZkGroupCipher(groupSecretParams);
|
const clientZkGroupCipher = getClientZkGroupCipher(groupSecretParams);
|
||||||
|
|
||||||
if (hasData(actions.sourceUuid)) {
|
if (!isByteBufferEmpty(actions.sourceUuid)) {
|
||||||
try {
|
try {
|
||||||
actions.sourceUuid = decryptUuid(
|
actions.sourceUuid = decryptUuid(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
@ -4752,7 +4749,7 @@ function decryptGroupChange(
|
||||||
// deleteMembers?: Array<GroupChangeClass.Actions.DeleteMemberAction>;
|
// deleteMembers?: Array<GroupChangeClass.Actions.DeleteMemberAction>;
|
||||||
actions.deleteMembers = compact(
|
actions.deleteMembers = compact(
|
||||||
(actions.deleteMembers || []).map(deleteMember => {
|
(actions.deleteMembers || []).map(deleteMember => {
|
||||||
if (hasData(deleteMember.deletedUserId)) {
|
if (!isByteBufferEmpty(deleteMember.deletedUserId)) {
|
||||||
try {
|
try {
|
||||||
deleteMember.deletedUserId = decryptUuid(
|
deleteMember.deletedUserId = decryptUuid(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
@ -4792,7 +4789,7 @@ function decryptGroupChange(
|
||||||
// modifyMemberRoles?: Array<GroupChangeClass.Actions.ModifyMemberRoleAction>;
|
// modifyMemberRoles?: Array<GroupChangeClass.Actions.ModifyMemberRoleAction>;
|
||||||
actions.modifyMemberRoles = compact(
|
actions.modifyMemberRoles = compact(
|
||||||
(actions.modifyMemberRoles || []).map(modifyMember => {
|
(actions.modifyMemberRoles || []).map(modifyMember => {
|
||||||
if (hasData(modifyMember.userId)) {
|
if (!isByteBufferEmpty(modifyMember.userId)) {
|
||||||
try {
|
try {
|
||||||
modifyMember.userId = decryptUuid(
|
modifyMember.userId = decryptUuid(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
@ -4840,7 +4837,7 @@ function decryptGroupChange(
|
||||||
// >;
|
// >;
|
||||||
actions.modifyMemberProfileKeys = compact(
|
actions.modifyMemberProfileKeys = compact(
|
||||||
(actions.modifyMemberProfileKeys || []).map(modifyMemberProfileKey => {
|
(actions.modifyMemberProfileKeys || []).map(modifyMemberProfileKey => {
|
||||||
if (hasData(modifyMemberProfileKey.presentation)) {
|
if (!isByteBufferEmpty(modifyMemberProfileKey.presentation)) {
|
||||||
const { profileKey, uuid } = decryptProfileKeyCredentialPresentation(
|
const { profileKey, uuid } = decryptProfileKeyCredentialPresentation(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
modifyMemberProfileKey.presentation.toArrayBuffer()
|
modifyMemberProfileKey.presentation.toArrayBuffer()
|
||||||
|
@ -4910,7 +4907,7 @@ function decryptGroupChange(
|
||||||
// >;
|
// >;
|
||||||
actions.deletePendingMembers = compact(
|
actions.deletePendingMembers = compact(
|
||||||
(actions.deletePendingMembers || []).map(deletePendingMember => {
|
(actions.deletePendingMembers || []).map(deletePendingMember => {
|
||||||
if (hasData(deletePendingMember.deletedUserId)) {
|
if (!isByteBufferEmpty(deletePendingMember.deletedUserId)) {
|
||||||
try {
|
try {
|
||||||
deletePendingMember.deletedUserId = decryptUuid(
|
deletePendingMember.deletedUserId = decryptUuid(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
@ -4952,7 +4949,7 @@ function decryptGroupChange(
|
||||||
// >;
|
// >;
|
||||||
actions.promotePendingMembers = compact(
|
actions.promotePendingMembers = compact(
|
||||||
(actions.promotePendingMembers || []).map(promotePendingMember => {
|
(actions.promotePendingMembers || []).map(promotePendingMember => {
|
||||||
if (hasData(promotePendingMember.presentation)) {
|
if (!isByteBufferEmpty(promotePendingMember.presentation)) {
|
||||||
const { profileKey, uuid } = decryptProfileKeyCredentialPresentation(
|
const { profileKey, uuid } = decryptProfileKeyCredentialPresentation(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
promotePendingMember.presentation.toArrayBuffer()
|
promotePendingMember.presentation.toArrayBuffer()
|
||||||
|
@ -4991,7 +4988,7 @@ function decryptGroupChange(
|
||||||
);
|
);
|
||||||
|
|
||||||
// modifyTitle?: GroupChangeClass.Actions.ModifyTitleAction;
|
// modifyTitle?: GroupChangeClass.Actions.ModifyTitleAction;
|
||||||
if (actions.modifyTitle && hasData(actions.modifyTitle.title)) {
|
if (actions.modifyTitle && !isByteBufferEmpty(actions.modifyTitle.title)) {
|
||||||
try {
|
try {
|
||||||
actions.modifyTitle.title = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
actions.modifyTitle.title = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
||||||
decryptGroupBlob(
|
decryptGroupBlob(
|
||||||
|
@ -5017,7 +5014,7 @@ function decryptGroupChange(
|
||||||
// GroupChangeClass.Actions.ModifyDisappearingMessagesTimerAction;
|
// GroupChangeClass.Actions.ModifyDisappearingMessagesTimerAction;
|
||||||
if (
|
if (
|
||||||
actions.modifyDisappearingMessagesTimer &&
|
actions.modifyDisappearingMessagesTimer &&
|
||||||
hasData(actions.modifyDisappearingMessagesTimer.timer)
|
!isByteBufferEmpty(actions.modifyDisappearingMessagesTimer.timer)
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
actions.modifyDisappearingMessagesTimer.timer = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
actions.modifyDisappearingMessagesTimer.timer = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
||||||
|
@ -5106,7 +5103,7 @@ function decryptGroupChange(
|
||||||
actions.deleteMemberPendingAdminApprovals = compact(
|
actions.deleteMemberPendingAdminApprovals = compact(
|
||||||
(actions.deleteMemberPendingAdminApprovals || []).map(
|
(actions.deleteMemberPendingAdminApprovals || []).map(
|
||||||
deletePendingApproval => {
|
deletePendingApproval => {
|
||||||
if (hasData(deletePendingApproval.deletedUserId)) {
|
if (!isByteBufferEmpty(deletePendingApproval.deletedUserId)) {
|
||||||
try {
|
try {
|
||||||
deletePendingApproval.deletedUserId = decryptUuid(
|
deletePendingApproval.deletedUserId = decryptUuid(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
@ -5150,7 +5147,7 @@ function decryptGroupChange(
|
||||||
actions.promoteMemberPendingAdminApprovals = compact(
|
actions.promoteMemberPendingAdminApprovals = compact(
|
||||||
(actions.promoteMemberPendingAdminApprovals || []).map(
|
(actions.promoteMemberPendingAdminApprovals || []).map(
|
||||||
promoteAdminApproval => {
|
promoteAdminApproval => {
|
||||||
if (hasData(promoteAdminApproval.userId)) {
|
if (!isByteBufferEmpty(promoteAdminApproval.userId)) {
|
||||||
try {
|
try {
|
||||||
promoteAdminApproval.userId = decryptUuid(
|
promoteAdminApproval.userId = decryptUuid(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
@ -5183,7 +5180,7 @@ function decryptGroupChange(
|
||||||
// modifyInviteLinkPassword?: GroupChangeClass.Actions.ModifyInviteLinkPasswordAction;
|
// modifyInviteLinkPassword?: GroupChangeClass.Actions.ModifyInviteLinkPasswordAction;
|
||||||
if (
|
if (
|
||||||
actions.modifyInviteLinkPassword &&
|
actions.modifyInviteLinkPassword &&
|
||||||
hasData(actions.modifyInviteLinkPassword.inviteLinkPassword)
|
!isByteBufferEmpty(actions.modifyInviteLinkPassword.inviteLinkPassword)
|
||||||
) {
|
) {
|
||||||
actions.modifyInviteLinkPassword.inviteLinkPassword = actions.modifyInviteLinkPassword.inviteLinkPassword.toString(
|
actions.modifyInviteLinkPassword.inviteLinkPassword = actions.modifyInviteLinkPassword.inviteLinkPassword.toString(
|
||||||
'base64'
|
'base64'
|
||||||
|
@ -5200,7 +5197,7 @@ export function decryptGroupTitle(
|
||||||
secretParams: string
|
secretParams: string
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
const clientZkGroupCipher = getClientZkGroupCipher(secretParams);
|
const clientZkGroupCipher = getClientZkGroupCipher(secretParams);
|
||||||
if (hasData(title)) {
|
if (!isByteBufferEmpty(title)) {
|
||||||
const blob = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
const blob = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
||||||
decryptGroupBlob(clientZkGroupCipher, title.toArrayBuffer())
|
decryptGroupBlob(clientZkGroupCipher, title.toArrayBuffer())
|
||||||
);
|
);
|
||||||
|
@ -5221,7 +5218,7 @@ function decryptGroupState(
|
||||||
const clientZkGroupCipher = getClientZkGroupCipher(groupSecretParams);
|
const clientZkGroupCipher = getClientZkGroupCipher(groupSecretParams);
|
||||||
|
|
||||||
// title
|
// title
|
||||||
if (hasData(groupState.title)) {
|
if (!isByteBufferEmpty(groupState.title)) {
|
||||||
try {
|
try {
|
||||||
groupState.title = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
groupState.title = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
||||||
decryptGroupBlob(clientZkGroupCipher, groupState.title.toArrayBuffer())
|
decryptGroupBlob(clientZkGroupCipher, groupState.title.toArrayBuffer())
|
||||||
|
@ -5241,7 +5238,7 @@ function decryptGroupState(
|
||||||
// Note: decryption happens during application of the change, on download of the avatar
|
// Note: decryption happens during application of the change, on download of the avatar
|
||||||
|
|
||||||
// disappearing message timer
|
// disappearing message timer
|
||||||
if (hasData(groupState.disappearingMessagesTimer)) {
|
if (!isByteBufferEmpty(groupState.disappearingMessagesTimer)) {
|
||||||
try {
|
try {
|
||||||
groupState.disappearingMessagesTimer = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
groupState.disappearingMessagesTimer = window.textsecure.protobuf.GroupAttributeBlob.decode(
|
||||||
decryptGroupBlob(
|
decryptGroupBlob(
|
||||||
|
@ -5314,7 +5311,7 @@ function decryptGroupState(
|
||||||
}
|
}
|
||||||
|
|
||||||
// inviteLinkPassword
|
// inviteLinkPassword
|
||||||
if (hasData(groupState.inviteLinkPassword)) {
|
if (!isByteBufferEmpty(groupState.inviteLinkPassword)) {
|
||||||
groupState.inviteLinkPassword = groupState.inviteLinkPassword.toString(
|
groupState.inviteLinkPassword = groupState.inviteLinkPassword.toString(
|
||||||
'base64'
|
'base64'
|
||||||
);
|
);
|
||||||
|
@ -5331,7 +5328,7 @@ function decryptMember(
|
||||||
logId: string
|
logId: string
|
||||||
) {
|
) {
|
||||||
// userId
|
// userId
|
||||||
if (hasData(member.userId)) {
|
if (!isByteBufferEmpty(member.userId)) {
|
||||||
try {
|
try {
|
||||||
member.userId = decryptUuid(
|
member.userId = decryptUuid(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
@ -5359,7 +5356,7 @@ function decryptMember(
|
||||||
}
|
}
|
||||||
|
|
||||||
// profileKey
|
// profileKey
|
||||||
if (hasData(member.profileKey)) {
|
if (!isByteBufferEmpty(member.profileKey)) {
|
||||||
member.profileKey = decryptProfileKey(
|
member.profileKey = decryptProfileKey(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
member.profileKey.toArrayBuffer(),
|
member.profileKey.toArrayBuffer(),
|
||||||
|
@ -5387,7 +5384,7 @@ function decryptMemberPendingProfileKey(
|
||||||
logId: string
|
logId: string
|
||||||
) {
|
) {
|
||||||
// addedByUserId
|
// addedByUserId
|
||||||
if (hasData(member.addedByUserId)) {
|
if (!isByteBufferEmpty(member.addedByUserId)) {
|
||||||
try {
|
try {
|
||||||
member.addedByUserId = decryptUuid(
|
member.addedByUserId = decryptUuid(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
@ -5433,7 +5430,7 @@ function decryptMemberPendingProfileKey(
|
||||||
const { userId, profileKey, role } = member.member;
|
const { userId, profileKey, role } = member.member;
|
||||||
|
|
||||||
// userId
|
// userId
|
||||||
if (hasData(userId)) {
|
if (!isByteBufferEmpty(userId)) {
|
||||||
try {
|
try {
|
||||||
member.member.userId = decryptUuid(
|
member.member.userId = decryptUuid(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
@ -5467,7 +5464,7 @@ function decryptMemberPendingProfileKey(
|
||||||
}
|
}
|
||||||
|
|
||||||
// profileKey
|
// profileKey
|
||||||
if (hasData(profileKey)) {
|
if (!isByteBufferEmpty(profileKey)) {
|
||||||
try {
|
try {
|
||||||
member.member.profileKey = decryptProfileKey(
|
member.member.profileKey = decryptProfileKey(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
@ -5512,7 +5509,7 @@ function decryptMemberPendingAdminApproval(
|
||||||
const { userId, profileKey } = member;
|
const { userId, profileKey } = member;
|
||||||
|
|
||||||
// userId
|
// userId
|
||||||
if (hasData(userId)) {
|
if (!isByteBufferEmpty(userId)) {
|
||||||
try {
|
try {
|
||||||
member.userId = decryptUuid(clientZkGroupCipher, userId.toArrayBuffer());
|
member.userId = decryptUuid(clientZkGroupCipher, userId.toArrayBuffer());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -5541,7 +5538,7 @@ function decryptMemberPendingAdminApproval(
|
||||||
}
|
}
|
||||||
|
|
||||||
// profileKey
|
// profileKey
|
||||||
if (hasData(profileKey)) {
|
if (!isByteBufferEmpty(profileKey)) {
|
||||||
try {
|
try {
|
||||||
member.profileKey = decryptProfileKey(
|
member.profileKey = decryptProfileKey(
|
||||||
clientZkGroupCipher,
|
clientZkGroupCipher,
|
||||||
|
|
|
@ -11,7 +11,10 @@ import * as path from 'path';
|
||||||
import pino from 'pino';
|
import pino from 'pino';
|
||||||
import { createStream } from 'rotating-file-stream';
|
import { createStream } from 'rotating-file-stream';
|
||||||
|
|
||||||
import { initLogger, LogLevel as SignalClientLogLevel } from 'libsignal-client';
|
import {
|
||||||
|
initLogger,
|
||||||
|
LogLevel as SignalClientLogLevel,
|
||||||
|
} from '@signalapp/signal-client';
|
||||||
|
|
||||||
import { uploadDebugLogs } from './debuglogs';
|
import { uploadDebugLogs } from './debuglogs';
|
||||||
import { redactAll } from '../../js/modules/privacy';
|
import { redactAll } from '../../js/modules/privacy';
|
||||||
|
@ -204,7 +207,7 @@ initLogger(
|
||||||
} else if (file) {
|
} else if (file) {
|
||||||
fileString = ` ${file}`;
|
fileString = ` ${file}`;
|
||||||
}
|
}
|
||||||
const logString = `libsignal-client ${message} ${target}${fileString}`;
|
const logString = `@signalapp/signal-client ${message} ${target}${fileString}`;
|
||||||
|
|
||||||
if (level === SignalClientLogLevel.Trace) {
|
if (level === SignalClientLogLevel.Trace) {
|
||||||
log.trace(logString);
|
log.trace(logString);
|
||||||
|
|
|
@ -48,6 +48,7 @@ import {
|
||||||
MessageTypeUnhydrated,
|
MessageTypeUnhydrated,
|
||||||
PreKeyType,
|
PreKeyType,
|
||||||
SearchResultMessageType,
|
SearchResultMessageType,
|
||||||
|
SenderKeyType,
|
||||||
ServerInterface,
|
ServerInterface,
|
||||||
SessionType,
|
SessionType,
|
||||||
SignedPreKeyType,
|
SignedPreKeyType,
|
||||||
|
@ -134,6 +135,11 @@ const dataInterface: ClientInterface = {
|
||||||
removeItemById,
|
removeItemById,
|
||||||
removeAllItems,
|
removeAllItems,
|
||||||
|
|
||||||
|
createOrUpdateSenderKey,
|
||||||
|
getSenderKeyById,
|
||||||
|
removeAllSenderKeys,
|
||||||
|
getAllSenderKeys,
|
||||||
|
|
||||||
createOrUpdateSession,
|
createOrUpdateSession,
|
||||||
createOrUpdateSessions,
|
createOrUpdateSessions,
|
||||||
getSessionById,
|
getSessionById,
|
||||||
|
@ -736,6 +742,23 @@ async function removeAllItems() {
|
||||||
await channels.removeAllItems();
|
await channels.removeAllItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sender Keys
|
||||||
|
|
||||||
|
async function createOrUpdateSenderKey(key: SenderKeyType): Promise<void> {
|
||||||
|
await channels.createOrUpdateSenderKey(key);
|
||||||
|
}
|
||||||
|
async function getSenderKeyById(
|
||||||
|
id: string
|
||||||
|
): Promise<SenderKeyType | undefined> {
|
||||||
|
return channels.getSenderKeyById(id);
|
||||||
|
}
|
||||||
|
async function removeAllSenderKeys(): Promise<void> {
|
||||||
|
await channels.removeAllSenderKeys();
|
||||||
|
}
|
||||||
|
async function getAllSenderKeys(): Promise<Array<SenderKeyType>> {
|
||||||
|
return channels.getAllSenderKeys();
|
||||||
|
}
|
||||||
|
|
||||||
// Sessions
|
// Sessions
|
||||||
|
|
||||||
async function createOrUpdateSession(data: SessionType) {
|
async function createOrUpdateSession(data: SessionType) {
|
||||||
|
|
|
@ -66,6 +66,16 @@ export type ClientSearchResultMessageType = MessageType & {
|
||||||
bodyRanges: [];
|
bodyRanges: [];
|
||||||
snippet: string;
|
snippet: string;
|
||||||
};
|
};
|
||||||
|
export type SenderKeyType = {
|
||||||
|
// Primary key
|
||||||
|
id: string;
|
||||||
|
// These two are combined into one string to give us the final id
|
||||||
|
senderId: string;
|
||||||
|
distributionId: string;
|
||||||
|
// Raw data to serialize/deserialize into signal-client SenderKeyRecord
|
||||||
|
data: Buffer;
|
||||||
|
lastUpdatedDate: number;
|
||||||
|
};
|
||||||
export type SessionType = {
|
export type SessionType = {
|
||||||
id: string;
|
id: string;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
@ -171,6 +181,11 @@ export type DataInterface = {
|
||||||
removeAllItems: () => Promise<void>;
|
removeAllItems: () => Promise<void>;
|
||||||
getAllItems: () => Promise<Array<ItemType>>;
|
getAllItems: () => Promise<Array<ItemType>>;
|
||||||
|
|
||||||
|
createOrUpdateSenderKey: (key: SenderKeyType) => Promise<void>;
|
||||||
|
getSenderKeyById: (id: string) => Promise<SenderKeyType | undefined>;
|
||||||
|
removeAllSenderKeys: () => Promise<void>;
|
||||||
|
getAllSenderKeys: () => Promise<Array<SenderKeyType>>;
|
||||||
|
|
||||||
createOrUpdateSession: (data: SessionType) => Promise<void>;
|
createOrUpdateSession: (data: SessionType) => Promise<void>;
|
||||||
createOrUpdateSessions: (array: Array<SessionType>) => Promise<void>;
|
createOrUpdateSessions: (array: Array<SessionType>) => Promise<void>;
|
||||||
getSessionById: (id: string) => Promise<SessionType | undefined>;
|
getSessionById: (id: string) => Promise<SessionType | undefined>;
|
||||||
|
|
|
@ -49,6 +49,7 @@ import {
|
||||||
MessageMetricsType,
|
MessageMetricsType,
|
||||||
PreKeyType,
|
PreKeyType,
|
||||||
SearchResultMessageType,
|
SearchResultMessageType,
|
||||||
|
SenderKeyType,
|
||||||
ServerInterface,
|
ServerInterface,
|
||||||
SessionType,
|
SessionType,
|
||||||
SignedPreKeyType,
|
SignedPreKeyType,
|
||||||
|
@ -86,7 +87,7 @@ type StickerRow = Readonly<{
|
||||||
|
|
||||||
type EmptyQuery = [];
|
type EmptyQuery = [];
|
||||||
type ArrayQuery = Array<Array<null | number | string>>;
|
type ArrayQuery = Array<Array<null | number | string>>;
|
||||||
type Query = { [key: string]: null | number | string };
|
type Query = { [key: string]: null | number | string | Buffer };
|
||||||
|
|
||||||
// Because we can't force this module to conform to an interface, we narrow our exports
|
// Because we can't force this module to conform to an interface, we narrow our exports
|
||||||
// to this one default export, which does conform to the interface.
|
// to this one default export, which does conform to the interface.
|
||||||
|
@ -125,6 +126,11 @@ const dataInterface: ServerInterface = {
|
||||||
removeItemById,
|
removeItemById,
|
||||||
removeAllItems,
|
removeAllItems,
|
||||||
|
|
||||||
|
createOrUpdateSenderKey,
|
||||||
|
getSenderKeyById,
|
||||||
|
removeAllSenderKeys,
|
||||||
|
getAllSenderKeys,
|
||||||
|
|
||||||
createOrUpdateSession,
|
createOrUpdateSession,
|
||||||
createOrUpdateSessions,
|
createOrUpdateSessions,
|
||||||
getSessionById,
|
getSessionById,
|
||||||
|
@ -1625,6 +1631,7 @@ async function updateToSchemaVersion25(currentVersion: number, db: Database) {
|
||||||
|
|
||||||
db.pragma('user_version = 25');
|
db.pragma('user_version = 25');
|
||||||
})();
|
})();
|
||||||
|
console.log('updateToSchemaVersion25: success!');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateToSchemaVersion26(currentVersion: number, db: Database) {
|
async function updateToSchemaVersion26(currentVersion: number, db: Database) {
|
||||||
|
@ -1660,6 +1667,7 @@ async function updateToSchemaVersion26(currentVersion: number, db: Database) {
|
||||||
|
|
||||||
db.pragma('user_version = 26');
|
db.pragma('user_version = 26');
|
||||||
})();
|
})();
|
||||||
|
console.log('updateToSchemaVersion26: success!');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateToSchemaVersion27(currentVersion: number, db: Database) {
|
async function updateToSchemaVersion27(currentVersion: number, db: Database) {
|
||||||
|
@ -1697,6 +1705,7 @@ async function updateToSchemaVersion27(currentVersion: number, db: Database) {
|
||||||
|
|
||||||
db.pragma('user_version = 27');
|
db.pragma('user_version = 27');
|
||||||
})();
|
})();
|
||||||
|
console.log('updateToSchemaVersion27: success!');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateToSchemaVersion28(currentVersion: number, db: Database) {
|
function updateToSchemaVersion28(currentVersion: number, db: Database) {
|
||||||
|
@ -1718,6 +1727,7 @@ function updateToSchemaVersion28(currentVersion: number, db: Database) {
|
||||||
|
|
||||||
db.pragma('user_version = 28');
|
db.pragma('user_version = 28');
|
||||||
})();
|
})();
|
||||||
|
console.log('updateToSchemaVersion28: success!');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateToSchemaVersion29(currentVersion: number, db: Database) {
|
function updateToSchemaVersion29(currentVersion: number, db: Database) {
|
||||||
|
@ -1751,6 +1761,28 @@ function updateToSchemaVersion29(currentVersion: number, db: Database) {
|
||||||
|
|
||||||
db.pragma('user_version = 29');
|
db.pragma('user_version = 29');
|
||||||
})();
|
})();
|
||||||
|
console.log('updateToSchemaVersion29: success!');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateToSchemaVersion30(currentVersion: number, db: Database) {
|
||||||
|
if (currentVersion >= 30) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.transaction(() => {
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE senderKeys(
|
||||||
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
|
senderId TEXT NOT NULL,
|
||||||
|
distributionId TEXT NOT NULL,
|
||||||
|
data BLOB NOT NULL,
|
||||||
|
lastUpdatedDate NUMBER NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.pragma('user_version = 30');
|
||||||
|
})();
|
||||||
|
console.log('updateToSchemaVersion30: success!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCHEMA_VERSIONS = [
|
const SCHEMA_VERSIONS = [
|
||||||
|
@ -1783,6 +1815,7 @@ const SCHEMA_VERSIONS = [
|
||||||
updateToSchemaVersion27,
|
updateToSchemaVersion27,
|
||||||
updateToSchemaVersion28,
|
updateToSchemaVersion28,
|
||||||
updateToSchemaVersion29,
|
updateToSchemaVersion29,
|
||||||
|
updateToSchemaVersion30,
|
||||||
];
|
];
|
||||||
|
|
||||||
function updateSchema(db: Database): void {
|
function updateSchema(db: Database): void {
|
||||||
|
@ -2087,6 +2120,49 @@ function removeAllItems(): Promise<void> {
|
||||||
return removeAllFromTable(ITEMS_TABLE);
|
return removeAllFromTable(ITEMS_TABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createOrUpdateSenderKey(key: SenderKeyType): Promise<void> {
|
||||||
|
const db = getInstance();
|
||||||
|
|
||||||
|
prepare(
|
||||||
|
db,
|
||||||
|
`
|
||||||
|
INSERT OR REPLACE INTO senderKeys (
|
||||||
|
id,
|
||||||
|
senderId,
|
||||||
|
distributionId,
|
||||||
|
data,
|
||||||
|
lastUpdatedDate
|
||||||
|
) values (
|
||||||
|
$id,
|
||||||
|
$senderId,
|
||||||
|
$distributionId,
|
||||||
|
$data,
|
||||||
|
$lastUpdatedDate
|
||||||
|
)
|
||||||
|
`
|
||||||
|
).run(key);
|
||||||
|
}
|
||||||
|
async function getSenderKeyById(
|
||||||
|
id: string
|
||||||
|
): Promise<SenderKeyType | undefined> {
|
||||||
|
const db = getInstance();
|
||||||
|
const row = prepare(db, 'SELECT * FROM senderKeys WHERE id = $id').get({
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
async function removeAllSenderKeys(): Promise<void> {
|
||||||
|
const db = getInstance();
|
||||||
|
prepare(db, 'DELETE FROM senderKeys').run({});
|
||||||
|
}
|
||||||
|
async function getAllSenderKeys(): Promise<Array<SenderKeyType>> {
|
||||||
|
const db = getInstance();
|
||||||
|
const rows = prepare(db, 'SELECT * FROM senderKeys').all({});
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
const SESSIONS_TABLE = 'sessions';
|
const SESSIONS_TABLE = 'sessions';
|
||||||
async function createOrUpdateSession(data: SessionType): Promise<void> {
|
async function createOrUpdateSession(data: SessionType): Promise<void> {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
|
@ -4635,6 +4711,7 @@ async function removeAll(): Promise<void> {
|
||||||
DELETE FROM items;
|
DELETE FROM items;
|
||||||
DELETE FROM messages;
|
DELETE FROM messages;
|
||||||
DELETE FROM preKeys;
|
DELETE FROM preKeys;
|
||||||
|
DELETE FROM senderKeys;
|
||||||
DELETE FROM sessions;
|
DELETE FROM sessions;
|
||||||
DELETE FROM signedPreKeys;
|
DELETE FROM signedPreKeys;
|
||||||
DELETE FROM unprocessed;
|
DELETE FROM unprocessed;
|
||||||
|
@ -4657,6 +4734,7 @@ async function removeAllConfiguration(): Promise<void> {
|
||||||
DELETE FROM identityKeys;
|
DELETE FROM identityKeys;
|
||||||
DELETE FROM items;
|
DELETE FROM items;
|
||||||
DELETE FROM preKeys;
|
DELETE FROM preKeys;
|
||||||
|
DELETE FROM senderKeys;
|
||||||
DELETE FROM sessions;
|
DELETE FROM sessions;
|
||||||
DELETE FROM signedPreKeys;
|
DELETE FROM signedPreKeys;
|
||||||
DELETE FROM unprocessed;
|
DELETE FROM unprocessed;
|
||||||
|
|
|
@ -37,6 +37,7 @@ type CleanedDataValue =
|
||||||
| boolean
|
| boolean
|
||||||
| null
|
| null
|
||||||
| undefined
|
| undefined
|
||||||
|
| Buffer
|
||||||
| CleanedObject
|
| CleanedObject
|
||||||
| CleanedArray;
|
| CleanedArray;
|
||||||
/* eslint-disable no-restricted-syntax */
|
/* eslint-disable no-restricted-syntax */
|
||||||
|
@ -110,6 +111,10 @@ function cleanDataInner(
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data instanceof Buffer) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
const dataAsRecord = data as Record<string, unknown>;
|
const dataAsRecord = data as Record<string, unknown>;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -61,6 +61,15 @@ describe('cleanDataForIpc', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps Buffers in a field', () => {
|
||||||
|
const buffer = Buffer.from('AABBCC', 'hex');
|
||||||
|
|
||||||
|
assert.deepEqual(cleanDataForIpc(buffer), {
|
||||||
|
cleaned: buffer,
|
||||||
|
pathsChanged: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('converts valid dates to ISO strings', () => {
|
it('converts valid dates to ISO strings', () => {
|
||||||
assert.deepEqual(cleanDataForIpc(new Date(924588548000)), {
|
assert.deepEqual(cleanDataForIpc(new Date(924588548000)), {
|
||||||
cleaned: '1999-04-20T06:09:08.000Z',
|
cleaned: '1999-04-20T06:09:08.000Z',
|
||||||
|
|
|
@ -4,7 +4,11 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { Direction, SessionRecord } from 'libsignal-client';
|
import {
|
||||||
|
Direction,
|
||||||
|
SenderKeyRecord,
|
||||||
|
SessionRecord,
|
||||||
|
} from '@signalapp/signal-client';
|
||||||
|
|
||||||
import { signal } from '../protobuf/compiled';
|
import { signal } from '../protobuf/compiled';
|
||||||
import { sessionStructureToArrayBuffer } from '../util/sessionTranslation';
|
import { sessionStructureToArrayBuffer } from '../util/sessionTranslation';
|
||||||
|
@ -14,7 +18,12 @@ import { clampPrivateKey, setPublicKeyTypeByte } from '../Curve';
|
||||||
import { SignalProtocolStore } from '../SignalProtocolStore';
|
import { SignalProtocolStore } from '../SignalProtocolStore';
|
||||||
import { IdentityKeyType, KeyPairType } from '../textsecure/Types.d';
|
import { IdentityKeyType, KeyPairType } from '../textsecure/Types.d';
|
||||||
|
|
||||||
const { RecordStructure, SessionStructure } = signal.proto.storage;
|
const {
|
||||||
|
RecordStructure,
|
||||||
|
SessionStructure,
|
||||||
|
SenderKeyRecordStructure,
|
||||||
|
SenderKeyStateStructure,
|
||||||
|
} = signal.proto.storage;
|
||||||
|
|
||||||
describe('SignalProtocolStore', () => {
|
describe('SignalProtocolStore', () => {
|
||||||
const number = '+5558675309';
|
const number = '+5558675309';
|
||||||
|
@ -47,6 +56,41 @@ describe('SignalProtocolStore', () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSenderKeyRecord(): SenderKeyRecord {
|
||||||
|
const proto = new SenderKeyRecordStructure();
|
||||||
|
|
||||||
|
const state = new SenderKeyStateStructure();
|
||||||
|
|
||||||
|
state.senderKeyId = 4;
|
||||||
|
|
||||||
|
const senderChainKey = new SenderKeyStateStructure.SenderChainKey();
|
||||||
|
|
||||||
|
senderChainKey.iteration = 10;
|
||||||
|
senderChainKey.seed = toUint8Array(getPublicKey());
|
||||||
|
state.senderChainKey = senderChainKey;
|
||||||
|
|
||||||
|
const senderSigningKey = new SenderKeyStateStructure.SenderSigningKey();
|
||||||
|
senderSigningKey.public = toUint8Array(getPublicKey());
|
||||||
|
senderSigningKey.private = toUint8Array(getPrivateKey());
|
||||||
|
|
||||||
|
state.senderSigningKey = senderSigningKey;
|
||||||
|
|
||||||
|
state.senderMessageKeys = [];
|
||||||
|
const messageKey = new SenderKeyStateStructure.SenderMessageKey();
|
||||||
|
messageKey.iteration = 234;
|
||||||
|
messageKey.seed = toUint8Array(getPublicKey());
|
||||||
|
state.senderMessageKeys.push(messageKey);
|
||||||
|
|
||||||
|
proto.senderKeyStates = [];
|
||||||
|
proto.senderKeyStates.push(state);
|
||||||
|
|
||||||
|
return SenderKeyRecord.deserialize(
|
||||||
|
Buffer.from(
|
||||||
|
signal.proto.storage.SenderKeyRecordStructure.encode(proto).finish()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function toUint8Array(buffer: ArrayBuffer): Uint8Array {
|
function toUint8Array(buffer: ArrayBuffer): Uint8Array {
|
||||||
return new Uint8Array(buffer);
|
return new Uint8Array(buffer);
|
||||||
}
|
}
|
||||||
|
@ -109,6 +153,49 @@ describe('SignalProtocolStore', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('senderKeys', () => {
|
||||||
|
it('roundtrips in memory', async () => {
|
||||||
|
const distributionId = window.getGuid();
|
||||||
|
const expected = getSenderKeyRecord();
|
||||||
|
|
||||||
|
const deviceId = 1;
|
||||||
|
const encodedAddress = `${number}.${deviceId}`;
|
||||||
|
|
||||||
|
await store.saveSenderKey(encodedAddress, distributionId, expected);
|
||||||
|
|
||||||
|
const actual = await store.getSenderKey(encodedAddress, distributionId);
|
||||||
|
if (!actual) {
|
||||||
|
throw new Error('getSenderKey returned nothing!');
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.isTrue(
|
||||||
|
constantTimeEqual(expected.serialize(), actual.serialize())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('roundtrips through database', async () => {
|
||||||
|
const distributionId = window.getGuid();
|
||||||
|
const expected = getSenderKeyRecord();
|
||||||
|
|
||||||
|
const deviceId = 1;
|
||||||
|
const encodedAddress = `${number}.${deviceId}`;
|
||||||
|
|
||||||
|
await store.saveSenderKey(encodedAddress, distributionId, expected);
|
||||||
|
|
||||||
|
// Re-fetch from the database to ensure we get the latest database value
|
||||||
|
await store.hydrateCaches();
|
||||||
|
|
||||||
|
const actual = await store.getSenderKey(encodedAddress, distributionId);
|
||||||
|
if (!actual) {
|
||||||
|
throw new Error('getSenderKey returned nothing!');
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.isTrue(
|
||||||
|
constantTimeEqual(expected.serialize(), actual.serialize())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('saveIdentity', () => {
|
describe('saveIdentity', () => {
|
||||||
const identifier = `${number}.1`;
|
const identifier = `${number}.1`;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
import { isByteBufferEmpty } from '../../util/isByteBufferEmpty';
|
||||||
|
|
||||||
|
describe('isByteBufferEmpty', () => {
|
||||||
|
it('returns true for undefined', () => {
|
||||||
|
assert.isTrue(isByteBufferEmpty(undefined));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true for object missing limit', () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const brokenByteBuffer: any = {};
|
||||||
|
|
||||||
|
assert.isTrue(isByteBufferEmpty(brokenByteBuffer));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true for object limit', () => {
|
||||||
|
const emptyByteBuffer = new window.dcodeIO.ByteBuffer(0);
|
||||||
|
|
||||||
|
assert.isTrue(isByteBufferEmpty(emptyByteBuffer));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for object limit', () => {
|
||||||
|
const byteBuffer = window.dcodeIO.ByteBuffer.wrap('AABBCC', 'hex');
|
||||||
|
|
||||||
|
assert.isFalse(isByteBufferEmpty(byteBuffer));
|
||||||
|
});
|
||||||
|
});
|
|
@ -573,6 +573,7 @@ export declare class ContentClass {
|
||||||
nullMessage?: NullMessageClass;
|
nullMessage?: NullMessageClass;
|
||||||
receiptMessage?: ReceiptMessageClass;
|
receiptMessage?: ReceiptMessageClass;
|
||||||
typingMessage?: TypingMessageClass;
|
typingMessage?: TypingMessageClass;
|
||||||
|
senderKeyDistributionMessage?: ByteBufferClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class DataMessageClass {
|
export declare class DataMessageClass {
|
||||||
|
@ -733,6 +734,7 @@ export declare namespace EnvelopeClass {
|
||||||
static PREKEY_BUNDLE: number;
|
static PREKEY_BUNDLE: number;
|
||||||
static RECEIPT: number;
|
static RECEIPT: number;
|
||||||
static UNIDENTIFIED_SENDER: number;
|
static UNIDENTIFIED_SENDER: number;
|
||||||
|
static SENDERKEY: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1345,7 +1347,7 @@ export declare namespace SenderCertificateClass {
|
||||||
) => Certificate;
|
) => Certificate;
|
||||||
toArrayBuffer: () => ArrayBuffer;
|
toArrayBuffer: () => ArrayBuffer;
|
||||||
|
|
||||||
sender?: string;
|
senderE164?: string;
|
||||||
senderUuid?: string;
|
senderUuid?: string;
|
||||||
senderDevice?: number;
|
senderDevice?: number;
|
||||||
expires?: ProtoBigNumberType;
|
expires?: ProtoBigNumberType;
|
||||||
|
@ -1377,6 +1379,8 @@ export declare namespace UnidentifiedSenderMessageClass {
|
||||||
type?: number;
|
type?: number;
|
||||||
senderCertificate?: SenderCertificateClass;
|
senderCertificate?: SenderCertificateClass;
|
||||||
content?: ProtoBinaryType;
|
content?: ProtoBinaryType;
|
||||||
|
contentHint?: number;
|
||||||
|
groupId?: ProtoBinaryType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1384,5 +1388,11 @@ export declare namespace UnidentifiedSenderMessageClass.Message {
|
||||||
class Type {
|
class Type {
|
||||||
static PREKEY_MESSAGE: number;
|
static PREKEY_MESSAGE: number;
|
||||||
static MESSAGE: number;
|
static MESSAGE: number;
|
||||||
|
static SENDERKEY_MESSAGE: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContentHint {
|
||||||
|
static SUPPLEMENTARY: number;
|
||||||
|
static RETRY: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,20 +14,25 @@ import PQueue from 'p-queue';
|
||||||
import { v4 as getGuid } from 'uuid';
|
import { v4 as getGuid } from 'uuid';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
groupDecrypt,
|
||||||
PreKeySignalMessage,
|
PreKeySignalMessage,
|
||||||
|
processSenderKeyDistributionMessage,
|
||||||
ProtocolAddress,
|
ProtocolAddress,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
SealedSenderDecryptionResult,
|
SealedSenderDecryptionResult,
|
||||||
sealedSenderDecryptMessage,
|
sealedSenderDecryptMessage,
|
||||||
sealedSenderDecryptToUsmc,
|
sealedSenderDecryptToUsmc,
|
||||||
|
SenderKeyDistributionMessage,
|
||||||
signalDecrypt,
|
signalDecrypt,
|
||||||
signalDecryptPreKey,
|
signalDecryptPreKey,
|
||||||
SignalMessage,
|
SignalMessage,
|
||||||
} from 'libsignal-client';
|
UnidentifiedSenderMessageContent,
|
||||||
|
} from '@signalapp/signal-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IdentityKeys,
|
IdentityKeys,
|
||||||
PreKeys,
|
PreKeys,
|
||||||
|
SenderKeys,
|
||||||
Sessions,
|
Sessions,
|
||||||
SignedPreKeys,
|
SignedPreKeys,
|
||||||
} from '../LibSignalStores';
|
} from '../LibSignalStores';
|
||||||
|
@ -43,6 +48,7 @@ import WebSocketResource, {
|
||||||
import Crypto from './Crypto';
|
import Crypto from './Crypto';
|
||||||
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
|
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
|
||||||
import { ContactBuffer, GroupBuffer } from './ContactsParser';
|
import { ContactBuffer, GroupBuffer } from './ContactsParser';
|
||||||
|
import { isByteBufferEmpty } from '../util/isByteBufferEmpty';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AttachmentPointerClass,
|
AttachmentPointerClass,
|
||||||
|
@ -56,6 +62,7 @@ import {
|
||||||
UnprocessedType,
|
UnprocessedType,
|
||||||
VerifiedClass,
|
VerifiedClass,
|
||||||
} from '../textsecure.d';
|
} from '../textsecure.d';
|
||||||
|
import { ByteBufferClass } from '../window.d';
|
||||||
|
|
||||||
import { WebSocket } from './WebSocket';
|
import { WebSocket } from './WebSocket';
|
||||||
|
|
||||||
|
@ -962,9 +969,12 @@ class MessageReceiverInner extends EventTarget {
|
||||||
|
|
||||||
async decrypt(
|
async decrypt(
|
||||||
envelope: EnvelopeClass,
|
envelope: EnvelopeClass,
|
||||||
ciphertext: any
|
ciphertext: ByteBufferClass
|
||||||
): Promise<ArrayBuffer | null> {
|
): Promise<ArrayBuffer | null> {
|
||||||
const { serverTrustRoot } = this;
|
const { serverTrustRoot } = this;
|
||||||
|
const envelopeTypeEnum = window.textsecure.protobuf.Envelope.Type;
|
||||||
|
const unidentifiedSenderTypeEnum =
|
||||||
|
window.textsecure.protobuf.UnidentifiedSenderMessage.Message.Type;
|
||||||
|
|
||||||
const identifier = envelope.sourceUuid || envelope.source;
|
const identifier = envelope.sourceUuid || envelope.source;
|
||||||
const { sourceDevice } = envelope;
|
const { sourceDevice } = envelope;
|
||||||
|
@ -989,7 +999,32 @@ class MessageReceiverInner extends EventTarget {
|
||||||
ArrayBuffer | { isMe: boolean } | { isBlocked: boolean } | undefined
|
ArrayBuffer | { isMe: boolean } | { isBlocked: boolean } | undefined
|
||||||
>;
|
>;
|
||||||
|
|
||||||
if (envelope.type === window.textsecure.protobuf.Envelope.Type.CIPHERTEXT) {
|
if (envelope.type === envelopeTypeEnum.SENDERKEY) {
|
||||||
|
window.log.info('sender key message from', this.getEnvelopeId(envelope));
|
||||||
|
if (!identifier) {
|
||||||
|
throw new Error(
|
||||||
|
'MessageReceiver.decrypt: No identifier for SENDERKEY message'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!sourceDevice) {
|
||||||
|
throw new Error(
|
||||||
|
'MessageReceiver.decrypt: No sourceDevice for SENDERKEY message'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderKeyStore = new SenderKeys();
|
||||||
|
const address = `${identifier}.${sourceDevice}`;
|
||||||
|
const messageBuffer = Buffer.from(ciphertext.toArrayBuffer());
|
||||||
|
promise = window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
||||||
|
address,
|
||||||
|
() =>
|
||||||
|
groupDecrypt(
|
||||||
|
ProtocolAddress.new(identifier, sourceDevice),
|
||||||
|
senderKeyStore,
|
||||||
|
messageBuffer
|
||||||
|
).then(plaintext => this.unpad(typedArrayToArrayBuffer(plaintext)))
|
||||||
|
);
|
||||||
|
} else if (envelope.type === envelopeTypeEnum.CIPHERTEXT) {
|
||||||
window.log.info('message from', this.getEnvelopeId(envelope));
|
window.log.info('message from', this.getEnvelopeId(envelope));
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -1016,9 +1051,7 @@ class MessageReceiverInner extends EventTarget {
|
||||||
identityKeyStore
|
identityKeyStore
|
||||||
).then(plaintext => this.unpad(typedArrayToArrayBuffer(plaintext)))
|
).then(plaintext => this.unpad(typedArrayToArrayBuffer(plaintext)))
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (envelope.type === envelopeTypeEnum.PREKEY_BUNDLE) {
|
||||||
envelope.type === window.textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE
|
|
||||||
) {
|
|
||||||
window.log.info('prekey message from', this.getEnvelopeId(envelope));
|
window.log.info('prekey message from', this.getEnvelopeId(envelope));
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -1047,17 +1080,14 @@ class MessageReceiverInner extends EventTarget {
|
||||||
signedPreKeyStore
|
signedPreKeyStore
|
||||||
).then(plaintext => this.unpad(typedArrayToArrayBuffer(plaintext)))
|
).then(plaintext => this.unpad(typedArrayToArrayBuffer(plaintext)))
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (envelope.type === envelopeTypeEnum.UNIDENTIFIED_SENDER) {
|
||||||
envelope.type ===
|
|
||||||
window.textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER
|
|
||||||
) {
|
|
||||||
window.log.info('received unidentified sender message');
|
window.log.info('received unidentified sender message');
|
||||||
const buffer = Buffer.from(ciphertext.toArrayBuffer());
|
const buffer = Buffer.from(ciphertext.toArrayBuffer());
|
||||||
|
|
||||||
const decryptSealedSender = async (): Promise<
|
const decryptSealedSender = async (): Promise<
|
||||||
SealedSenderDecryptionResult | null | { isBlocked: true }
|
SealedSenderDecryptionResult | Buffer | null | { isBlocked: true }
|
||||||
> => {
|
> => {
|
||||||
const messageContent = await sealedSenderDecryptToUsmc(
|
const messageContent: UnidentifiedSenderMessageContent = await sealedSenderDecryptToUsmc(
|
||||||
buffer,
|
buffer,
|
||||||
identityKeyStore
|
identityKeyStore
|
||||||
);
|
);
|
||||||
|
@ -1101,6 +1131,30 @@ class MessageReceiverInner extends EventTarget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
messageContent.msgType() ===
|
||||||
|
unidentifiedSenderTypeEnum.SENDERKEY_MESSAGE
|
||||||
|
) {
|
||||||
|
const sealedSenderIdentifier = certificate.senderUuid();
|
||||||
|
const sealedSenderSourceDevice = certificate.senderDeviceId();
|
||||||
|
const senderKeyStore = new SenderKeys();
|
||||||
|
|
||||||
|
const address = `${sealedSenderIdentifier}.${sealedSenderSourceDevice}`;
|
||||||
|
|
||||||
|
return window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
||||||
|
address,
|
||||||
|
() =>
|
||||||
|
groupDecrypt(
|
||||||
|
ProtocolAddress.new(
|
||||||
|
sealedSenderIdentifier,
|
||||||
|
sealedSenderSourceDevice
|
||||||
|
),
|
||||||
|
senderKeyStore,
|
||||||
|
buffer
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const sealedSenderIdentifier = envelope.sourceUuid || envelope.source;
|
const sealedSenderIdentifier = envelope.sourceUuid || envelope.source;
|
||||||
const address = `${sealedSenderIdentifier}.${envelope.sourceDevice}`;
|
const address = `${sealedSenderIdentifier}.${envelope.sourceDevice}`;
|
||||||
return window.textsecure.storage.protocol.enqueueSessionJob(
|
return window.textsecure.storage.protocol.enqueueSessionJob(
|
||||||
|
@ -1128,6 +1182,9 @@ class MessageReceiverInner extends EventTarget {
|
||||||
if ('isBlocked' in result) {
|
if ('isBlocked' in result) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
if (result instanceof Buffer) {
|
||||||
|
return this.unpad(typedArrayToArrayBuffer(result));
|
||||||
|
}
|
||||||
|
|
||||||
const content = typedArrayToArrayBuffer(result.message());
|
const content = typedArrayToArrayBuffer(result.message());
|
||||||
|
|
||||||
|
@ -1390,7 +1447,10 @@ class MessageReceiverInner extends EventTarget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDataMessage(envelope: EnvelopeClass, msg: DataMessageClass) {
|
async handleDataMessage(
|
||||||
|
envelope: EnvelopeClass,
|
||||||
|
msg: DataMessageClass
|
||||||
|
): Promise<void> {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'MessageReceiver.handleDataMessage',
|
'MessageReceiver.handleDataMessage',
|
||||||
this.getEnvelopeId(envelope)
|
this.getEnvelopeId(envelope)
|
||||||
|
@ -1519,35 +1579,101 @@ class MessageReceiverInner extends EventTarget {
|
||||||
async innerHandleContentMessage(
|
async innerHandleContentMessage(
|
||||||
envelope: EnvelopeClass,
|
envelope: EnvelopeClass,
|
||||||
plaintext: ArrayBuffer
|
plaintext: ArrayBuffer
|
||||||
) {
|
): Promise<void> {
|
||||||
const content = window.textsecure.protobuf.Content.decode(plaintext);
|
const content = window.textsecure.protobuf.Content.decode(plaintext);
|
||||||
|
|
||||||
|
// Note: a distribution message can be tacked on to any other message, so we
|
||||||
|
// make sure to process it first. If that fails, we still try to process
|
||||||
|
// the rest of the message.
|
||||||
|
try {
|
||||||
|
if (content.senderKeyDistributionMessage) {
|
||||||
|
await this.handleSenderKeyDistributionMessage(
|
||||||
|
envelope,
|
||||||
|
content.senderKeyDistributionMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorString = error && error.stack ? error.stack : error;
|
||||||
|
window.log.error(
|
||||||
|
`innerHandleContentMessage: Failed to process sender key distribution message: ${errorString}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (content.syncMessage) {
|
if (content.syncMessage) {
|
||||||
return this.handleSyncMessage(envelope, content.syncMessage);
|
await this.handleSyncMessage(envelope, content.syncMessage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (content.dataMessage) {
|
if (content.dataMessage) {
|
||||||
return this.handleDataMessage(envelope, content.dataMessage);
|
await this.handleDataMessage(envelope, content.dataMessage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (content.nullMessage) {
|
if (content.nullMessage) {
|
||||||
this.handleNullMessage(envelope);
|
await this.handleNullMessage(envelope);
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
if (content.callingMessage) {
|
if (content.callingMessage) {
|
||||||
return this.handleCallingMessage(envelope, content.callingMessage);
|
await this.handleCallingMessage(envelope, content.callingMessage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (content.receiptMessage) {
|
if (content.receiptMessage) {
|
||||||
return this.handleReceiptMessage(envelope, content.receiptMessage);
|
await this.handleReceiptMessage(envelope, content.receiptMessage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (content.typingMessage) {
|
if (content.typingMessage) {
|
||||||
return this.handleTypingMessage(envelope, content.typingMessage);
|
await this.handleTypingMessage(envelope, content.typingMessage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
throw new Error('Unsupported content message');
|
|
||||||
|
if (isByteBufferEmpty(content.senderKeyDistributionMessage)) {
|
||||||
|
throw new Error('Unsupported content message');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleSenderKeyDistributionMessage(
|
||||||
|
envelope: EnvelopeClass,
|
||||||
|
distributionMessage: ByteBufferClass
|
||||||
|
): Promise<void> {
|
||||||
|
const envelopeId = this.getEnvelopeId(envelope);
|
||||||
|
window.log.info(`handleSenderKeyDistributionMessage: ${envelopeId}`);
|
||||||
|
|
||||||
|
// Note: we don't call removeFromCache here because this message can be combined
|
||||||
|
// with a dataMessage, for example. That processing will dictate cache removal.
|
||||||
|
|
||||||
|
const identifier = envelope.sourceUuid || envelope.source;
|
||||||
|
const { sourceDevice } = envelope;
|
||||||
|
if (!identifier) {
|
||||||
|
throw new Error(
|
||||||
|
`handleSenderKeyDistributionMessage: No identifier for envelope ${envelopeId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!isNumber(sourceDevice)) {
|
||||||
|
throw new Error(
|
||||||
|
`handleSenderKeyDistributionMessage: Missing sourceDevice for envelope ${envelopeId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sender = ProtocolAddress.new(identifier, sourceDevice);
|
||||||
|
const senderKeyDistributionMessage = SenderKeyDistributionMessage.deserialize(
|
||||||
|
Buffer.from(distributionMessage.toArrayBuffer())
|
||||||
|
);
|
||||||
|
const senderKeyStore = new SenderKeys();
|
||||||
|
const address = `${identifier}.${sourceDevice}`;
|
||||||
|
|
||||||
|
await window.textsecure.storage.protocol.enqueueSenderKeyJob(address, () =>
|
||||||
|
processSenderKeyDistributionMessage(
|
||||||
|
sender,
|
||||||
|
senderKeyDistributionMessage,
|
||||||
|
senderKeyStore
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCallingMessage(
|
async handleCallingMessage(
|
||||||
envelope: EnvelopeClass,
|
envelope: EnvelopeClass,
|
||||||
callingMessage: CallingMessageClass
|
callingMessage: CallingMessageClass
|
||||||
) {
|
): Promise<void> {
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
await window.Signal.Services.calling.handleCallingMessage(
|
await window.Signal.Services.calling.handleCallingMessage(
|
||||||
envelope,
|
envelope,
|
||||||
|
@ -1558,7 +1684,7 @@ class MessageReceiverInner extends EventTarget {
|
||||||
async handleReceiptMessage(
|
async handleReceiptMessage(
|
||||||
envelope: EnvelopeClass,
|
envelope: EnvelopeClass,
|
||||||
receiptMessage: ReceiptMessageClass
|
receiptMessage: ReceiptMessageClass
|
||||||
) {
|
): Promise<void> {
|
||||||
const results = [];
|
const results = [];
|
||||||
if (
|
if (
|
||||||
receiptMessage.type ===
|
receiptMessage.type ===
|
||||||
|
@ -1593,13 +1719,13 @@ class MessageReceiverInner extends EventTarget {
|
||||||
results.push(this.dispatchAndWait(ev));
|
results.push(this.dispatchAndWait(ev));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.all(results);
|
await Promise.all(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleTypingMessage(
|
async handleTypingMessage(
|
||||||
envelope: EnvelopeClass,
|
envelope: EnvelopeClass,
|
||||||
typingMessage: TypingMessageClass
|
typingMessage: TypingMessageClass
|
||||||
) {
|
): Promise<void> {
|
||||||
const ev = new Event('typing');
|
const ev = new Event('typing');
|
||||||
|
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
|
@ -1612,7 +1738,7 @@ class MessageReceiverInner extends EventTarget {
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
`Typing message envelope timestamp (${envelopeTimestamp}) did not match typing timestamp (${typingTimestamp})`
|
`Typing message envelope timestamp (${envelopeTimestamp}) did not match typing timestamp (${typingTimestamp})`
|
||||||
);
|
);
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1645,10 +1771,10 @@ class MessageReceiverInner extends EventTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.dispatchEvent(ev);
|
await this.dispatchEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNullMessage(envelope: EnvelopeClass) {
|
handleNullMessage(envelope: EnvelopeClass): void {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'MessageReceiver.handleNullMessage',
|
'MessageReceiver.handleNullMessage',
|
||||||
this.getEnvelopeId(envelope)
|
this.getEnvelopeId(envelope)
|
||||||
|
@ -1783,7 +1909,7 @@ class MessageReceiverInner extends EventTarget {
|
||||||
async handleSyncMessage(
|
async handleSyncMessage(
|
||||||
envelope: EnvelopeClass,
|
envelope: EnvelopeClass,
|
||||||
syncMessage: SyncMessageClass
|
syncMessage: SyncMessageClass
|
||||||
) {
|
): Promise<void> {
|
||||||
const unidentified = syncMessage.sent
|
const unidentified = syncMessage.sent
|
||||||
? syncMessage.sent.unidentifiedStatus || []
|
? syncMessage.sent.unidentifiedStatus || []
|
||||||
: [];
|
: [];
|
||||||
|
@ -2026,7 +2152,7 @@ class MessageReceiverInner extends EventTarget {
|
||||||
async handleRead(
|
async handleRead(
|
||||||
envelope: EnvelopeClass,
|
envelope: EnvelopeClass,
|
||||||
read: Array<SyncMessageClass.Read>
|
read: Array<SyncMessageClass.Read>
|
||||||
) {
|
): Promise<void> {
|
||||||
window.log.info('MessageReceiver.handleRead', this.getEnvelopeId(envelope));
|
window.log.info('MessageReceiver.handleRead', this.getEnvelopeId(envelope));
|
||||||
const results = [];
|
const results = [];
|
||||||
for (let i = 0; i < read.length; i += 1) {
|
for (let i = 0; i < read.length; i += 1) {
|
||||||
|
@ -2046,7 +2172,7 @@ class MessageReceiverInner extends EventTarget {
|
||||||
);
|
);
|
||||||
results.push(this.dispatchAndWait(ev));
|
results.push(this.dispatchAndWait(ev));
|
||||||
}
|
}
|
||||||
return Promise.all(results);
|
await Promise.all(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContacts(envelope: EnvelopeClass, contacts: SyncMessageClass.Contacts) {
|
handleContacts(envelope: EnvelopeClass, contacts: SyncMessageClass.Contacts) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
sealedSenderEncryptMessage,
|
sealedSenderEncryptMessage,
|
||||||
SenderCertificate,
|
SenderCertificate,
|
||||||
signalEncrypt,
|
signalEncrypt,
|
||||||
} from 'libsignal-client';
|
} from '@signalapp/signal-client';
|
||||||
|
|
||||||
import { ServerKeysType, WebAPIType } from './WebAPI';
|
import { ServerKeysType, WebAPIType } from './WebAPI';
|
||||||
import { ContentClass, DataMessageClass } from '../textsecure.d';
|
import { ContentClass, DataMessageClass } from '../textsecure.d';
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
|
|
||||||
export {
|
export {
|
||||||
IdentityKeyType,
|
IdentityKeyType,
|
||||||
SignedPreKeyType,
|
|
||||||
PreKeyType,
|
PreKeyType,
|
||||||
|
SenderKeyType,
|
||||||
|
SessionType,
|
||||||
|
SignedPreKeyType,
|
||||||
UnprocessedType,
|
UnprocessedType,
|
||||||
UnprocessedUpdateType,
|
UnprocessedUpdateType,
|
||||||
SessionType,
|
|
||||||
} from '../sql/Interface';
|
} from '../sql/Interface';
|
||||||
|
|
||||||
// How the legacy APIs generate these types
|
// How the legacy APIs generate these types
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2019-2021 Signal Messenger, LLC
|
// Copyright 2019-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { PrivateKey, PublicKey } from 'libsignal-client';
|
import { PrivateKey, PublicKey } from '@signalapp/signal-client';
|
||||||
|
|
||||||
export function keyPair(): Record<string, Buffer> {
|
export function keyPair(): Record<string, Buffer> {
|
||||||
const privKey = PrivateKey.generate();
|
const privKey = PrivateKey.generate();
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { isNumber } from 'lodash';
|
||||||
|
|
||||||
|
import { ByteBufferClass } from '../window.d';
|
||||||
|
|
||||||
|
export function isByteBufferEmpty(data?: ByteBufferClass): boolean {
|
||||||
|
return !data || !isNumber(data.limit) || data.limit === 0;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { PublicKey, Fingerprint } from 'libsignal-client';
|
import { PublicKey, Fingerprint } from '@signalapp/signal-client';
|
||||||
import { ConversationType } from '../state/ducks/conversations';
|
import { ConversationType } from '../state/ducks/conversations';
|
||||||
|
|
||||||
export async function generateSecurityNumber(
|
export async function generateSecurityNumber(
|
||||||
|
|
|
@ -17,7 +17,7 @@ const EXTERNAL_MODULE = new Set([
|
||||||
'fsevents',
|
'fsevents',
|
||||||
'got',
|
'got',
|
||||||
'jquery',
|
'jquery',
|
||||||
'libsignal-client',
|
'@signalapp/signal-client',
|
||||||
'node-fetch',
|
'node-fetch',
|
||||||
'node-sass',
|
'node-sass',
|
||||||
'pino',
|
'pino',
|
||||||
|
|
21
yarn.lock
21
yarn.lock
|
@ -1467,6 +1467,14 @@
|
||||||
react-lifecycles-compat "^3.0.4"
|
react-lifecycles-compat "^3.0.4"
|
||||||
warning "^3.0.0"
|
warning "^3.0.0"
|
||||||
|
|
||||||
|
"@signalapp/signal-client@0.5.1":
|
||||||
|
version "0.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@signalapp/signal-client/-/signal-client-0.5.1.tgz#b893a658db92f7fe3d3657ac9a4f83909ac1d09d"
|
||||||
|
integrity sha512-d3wM2vS4IcPGmBzcjigD1Y14J3j4rP+dTpE1J5xrPfknLgGPXLR+dX4I6RU9nFVe5toCyrRnTSBjQbBn/SixKA==
|
||||||
|
dependencies:
|
||||||
|
node-gyp-build "^4.2.3"
|
||||||
|
uuid "^8.3.0"
|
||||||
|
|
||||||
"@sindresorhus/is@0.8.0":
|
"@sindresorhus/is@0.8.0":
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.8.0.tgz#073aee40b0aab2d4ace33c0a2a2672a37da6fa12"
|
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.8.0.tgz#073aee40b0aab2d4ace33c0a2a2672a37da6fa12"
|
||||||
|
@ -11134,12 +11142,6 @@ levn@~0.3.0:
|
||||||
prelude-ls "~1.1.2"
|
prelude-ls "~1.1.2"
|
||||||
type-check "~0.3.2"
|
type-check "~0.3.2"
|
||||||
|
|
||||||
"libsignal-client@https://github.com/signalapp/libsignal-client-node.git#afa8f40eaa218b7fff94278d9b2ab2e13a2ee04b":
|
|
||||||
version "0.3.3"
|
|
||||||
resolved "https://github.com/signalapp/libsignal-client-node.git#afa8f40eaa218b7fff94278d9b2ab2e13a2ee04b"
|
|
||||||
dependencies:
|
|
||||||
bindings "^1.5.0"
|
|
||||||
|
|
||||||
lie@*:
|
lie@*:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.2.0.tgz#4f13f2f8bbb027d383db338c43043545791aa8dc"
|
resolved "https://registry.yarnpkg.com/lie/-/lie-3.2.0.tgz#4f13f2f8bbb027d383db338c43043545791aa8dc"
|
||||||
|
@ -12339,7 +12341,7 @@ node-forge@0.10.0, node-forge@^0.10.0:
|
||||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
|
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
|
||||||
integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
|
integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
|
||||||
|
|
||||||
node-gyp-build@^4.2.1:
|
node-gyp-build@^4.2.1, node-gyp-build@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
|
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
|
||||||
integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==
|
integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==
|
||||||
|
@ -18025,6 +18027,11 @@ uuid@^3.4.0:
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||||
|
|
||||||
|
uuid@^8.3.0:
|
||||||
|
version "8.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
v8-compile-cache@^2.0.3:
|
v8-compile-cache@^2.0.3:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
|
||||||
|
|
Loading…
Reference in New Issue