Extra toast for Message Receiver errors
This commit is contained in:
parent
36b3e2de08
commit
ca4aad6bad
4 changed files with 96 additions and 11 deletions
|
@ -88,6 +88,7 @@ import type {
|
||||||
ErrorEvent,
|
ErrorEvent,
|
||||||
FetchLatestEvent,
|
FetchLatestEvent,
|
||||||
GroupEvent,
|
GroupEvent,
|
||||||
|
InvalidPlaintextEvent,
|
||||||
KeysEvent,
|
KeysEvent,
|
||||||
MessageEvent,
|
MessageEvent,
|
||||||
MessageEventData,
|
MessageEventData,
|
||||||
|
@ -140,7 +141,11 @@ import * as Conversation from './types/Conversation';
|
||||||
import * as Stickers from './types/Stickers';
|
import * as Stickers from './types/Stickers';
|
||||||
import * as Errors from './types/errors';
|
import * as Errors from './types/errors';
|
||||||
import { SignalService as Proto } from './protobuf';
|
import { SignalService as Proto } from './protobuf';
|
||||||
import { onRetryRequest, onDecryptionError } from './util/handleRetry';
|
import {
|
||||||
|
onRetryRequest,
|
||||||
|
onDecryptionError,
|
||||||
|
onInvalidPlaintextMessage,
|
||||||
|
} from './util/handleRetry';
|
||||||
import { themeChanged } from './shims/themeChanged';
|
import { themeChanged } from './shims/themeChanged';
|
||||||
import { createIPCEvents } from './util/createIPCEvents';
|
import { createIPCEvents } from './util/createIPCEvents';
|
||||||
import { RemoveAllConfiguration } from './types/RemoveAllConfiguration';
|
import { RemoveAllConfiguration } from './types/RemoveAllConfiguration';
|
||||||
|
@ -390,6 +395,14 @@ export async function startApp(): Promise<void> {
|
||||||
drop(onDecryptionErrorQueue.add(() => onDecryptionError(event)));
|
drop(onDecryptionErrorQueue.add(() => onDecryptionError(event)));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
messageReceiver.addEventListener(
|
||||||
|
'invalid-plaintext',
|
||||||
|
queuedEventListener((event: InvalidPlaintextEvent): void => {
|
||||||
|
drop(
|
||||||
|
onDecryptionErrorQueue.add(() => onInvalidPlaintextMessage(event))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
messageReceiver.addEventListener(
|
messageReceiver.addEventListener(
|
||||||
'retry-request',
|
'retry-request',
|
||||||
queuedEventListener((event: RetryRequestEvent): void => {
|
queuedEventListener((event: RetryRequestEvent): void => {
|
||||||
|
|
|
@ -96,6 +96,7 @@ import {
|
||||||
DecryptionErrorEvent,
|
DecryptionErrorEvent,
|
||||||
SentEvent,
|
SentEvent,
|
||||||
ProfileKeyUpdateEvent,
|
ProfileKeyUpdateEvent,
|
||||||
|
InvalidPlaintextEvent,
|
||||||
MessageEvent,
|
MessageEvent,
|
||||||
RetryRequestEvent,
|
RetryRequestEvent,
|
||||||
ReadEvent,
|
ReadEvent,
|
||||||
|
@ -160,6 +161,11 @@ type DecryptSealedSenderResult = Readonly<{
|
||||||
unsealedPlaintext?: SealedSenderDecryptionResult;
|
unsealedPlaintext?: SealedSenderDecryptionResult;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
type InnerDecryptResultType = Readonly<{
|
||||||
|
plaintext: Uint8Array;
|
||||||
|
wasEncrypted: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
type CacheAddItemType = {
|
type CacheAddItemType = {
|
||||||
envelope: ProcessedEnvelope;
|
envelope: ProcessedEnvelope;
|
||||||
data: UnprocessedType;
|
data: UnprocessedType;
|
||||||
|
@ -533,6 +539,11 @@ export default class MessageReceiver
|
||||||
handler: (ev: DecryptionErrorEvent) => void
|
handler: (ev: DecryptionErrorEvent) => void
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
|
public override addEventListener(
|
||||||
|
name: 'invalid-plaintext',
|
||||||
|
handler: (ev: InvalidPlaintextEvent) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
public override addEventListener(
|
public override addEventListener(
|
||||||
name: 'sent',
|
name: 'sent',
|
||||||
handler: (ev: SentEvent) => void
|
handler: (ev: SentEvent) => void
|
||||||
|
@ -1395,18 +1406,20 @@ export default class MessageReceiver
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(logId);
|
log.info(logId);
|
||||||
const plaintext = await this.decrypt(
|
const decryptResult = await this.decrypt(
|
||||||
stores,
|
stores,
|
||||||
envelope,
|
envelope,
|
||||||
ciphertext,
|
ciphertext,
|
||||||
uuidKind
|
uuidKind
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!plaintext) {
|
if (!decryptResult) {
|
||||||
log.warn(`${logId}: plaintext was falsey`);
|
log.warn(`${logId}: plaintext was falsey`);
|
||||||
return { plaintext, envelope };
|
return { plaintext: undefined, envelope };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { plaintext, wasEncrypted } = decryptResult;
|
||||||
|
|
||||||
// Note: we need to process this as part of decryption, because we might need this
|
// Note: we need to process this as part of decryption, because we might need this
|
||||||
// sender key to decrypt the next message in the queue!
|
// sender key to decrypt the next message in the queue!
|
||||||
let isGroupV2 = false;
|
let isGroupV2 = false;
|
||||||
|
@ -1414,6 +1427,32 @@ export default class MessageReceiver
|
||||||
let inProgressMessageType = '';
|
let inProgressMessageType = '';
|
||||||
try {
|
try {
|
||||||
const content = Proto.Content.decode(plaintext);
|
const content = Proto.Content.decode(plaintext);
|
||||||
|
if (!wasEncrypted && Bytes.isEmpty(content.decryptionErrorMessage)) {
|
||||||
|
log.warn(
|
||||||
|
`${logId}: dropping plaintext envelope without decryption error message`
|
||||||
|
);
|
||||||
|
|
||||||
|
const event = new InvalidPlaintextEvent({
|
||||||
|
senderDevice: envelope.sourceDevice ?? 1,
|
||||||
|
senderUuid: envelope.sourceUuid,
|
||||||
|
timestamp: envelope.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.removeFromCache(envelope);
|
||||||
|
|
||||||
|
const envelopeId = getEnvelopeId(envelope);
|
||||||
|
|
||||||
|
// Avoid deadlocks by scheduling processing on decrypted queue
|
||||||
|
drop(
|
||||||
|
this.addToQueue(
|
||||||
|
async () => this.dispatchEvent(event),
|
||||||
|
`decrypted/dispatchEvent/InvalidPlaintextEvent(${envelopeId})`,
|
||||||
|
TaskType.Decrypted
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return { plaintext: undefined, envelope };
|
||||||
|
}
|
||||||
|
|
||||||
isGroupV2 =
|
isGroupV2 =
|
||||||
Boolean(content.dataMessage?.groupV2) ||
|
Boolean(content.dataMessage?.groupV2) ||
|
||||||
|
@ -1742,7 +1781,7 @@ export default class MessageReceiver
|
||||||
envelope: UnsealedEnvelope,
|
envelope: UnsealedEnvelope,
|
||||||
ciphertext: Uint8Array,
|
ciphertext: Uint8Array,
|
||||||
uuidKind: UUIDKind
|
uuidKind: UUIDKind
|
||||||
): Promise<Uint8Array | undefined> {
|
): Promise<InnerDecryptResultType | undefined> {
|
||||||
const { sessionStore, identityKeyStore, zone } = stores;
|
const { sessionStore, identityKeyStore, zone } = stores;
|
||||||
|
|
||||||
const logId = getEnvelopeId(envelope);
|
const logId = getEnvelopeId(envelope);
|
||||||
|
@ -1784,7 +1823,10 @@ export default class MessageReceiver
|
||||||
const buffer = Buffer.from(ciphertext);
|
const buffer = Buffer.from(ciphertext);
|
||||||
const plaintextContent = PlaintextContent.deserialize(buffer);
|
const plaintextContent = PlaintextContent.deserialize(buffer);
|
||||||
|
|
||||||
return this.unpad(plaintextContent.body());
|
return {
|
||||||
|
plaintext: this.unpad(plaintextContent.body()),
|
||||||
|
wasEncrypted: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (envelope.type === envelopeTypeEnum.CIPHERTEXT) {
|
if (envelope.type === envelopeTypeEnum.CIPHERTEXT) {
|
||||||
log.info(`decrypt/${logId}: ciphertext message`);
|
log.info(`decrypt/${logId}: ciphertext message`);
|
||||||
|
@ -1813,7 +1855,7 @@ export default class MessageReceiver
|
||||||
),
|
),
|
||||||
zone
|
zone
|
||||||
);
|
);
|
||||||
return plaintext;
|
return { plaintext, wasEncrypted: true };
|
||||||
}
|
}
|
||||||
if (envelope.type === envelopeTypeEnum.PREKEY_BUNDLE) {
|
if (envelope.type === envelopeTypeEnum.PREKEY_BUNDLE) {
|
||||||
log.info(`decrypt/${logId}: prekey message`);
|
log.info(`decrypt/${logId}: prekey message`);
|
||||||
|
@ -1846,7 +1888,7 @@ export default class MessageReceiver
|
||||||
),
|
),
|
||||||
zone
|
zone
|
||||||
);
|
);
|
||||||
return plaintext;
|
return { plaintext, wasEncrypted: true };
|
||||||
}
|
}
|
||||||
if (envelope.type === envelopeTypeEnum.UNIDENTIFIED_SENDER) {
|
if (envelope.type === envelopeTypeEnum.UNIDENTIFIED_SENDER) {
|
||||||
log.info(`decrypt/${logId}: unidentified message`);
|
log.info(`decrypt/${logId}: unidentified message`);
|
||||||
|
@ -1857,7 +1899,7 @@ export default class MessageReceiver
|
||||||
);
|
);
|
||||||
|
|
||||||
if (plaintext) {
|
if (plaintext) {
|
||||||
return this.unpad(plaintext);
|
return { plaintext: this.unpad(plaintext), wasEncrypted: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unsealedPlaintext) {
|
if (unsealedPlaintext) {
|
||||||
|
@ -1871,7 +1913,7 @@ export default class MessageReceiver
|
||||||
|
|
||||||
// Return just the content because that matches the signature of the other
|
// Return just the content because that matches the signature of the other
|
||||||
// decrypt methods used above.
|
// decrypt methods used above.
|
||||||
return this.unpad(content);
|
return { plaintext: this.unpad(content), wasEncrypted: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Unexpected lack of plaintext from unidentified sender');
|
throw new Error('Unexpected lack of plaintext from unidentified sender');
|
||||||
|
@ -1884,7 +1926,7 @@ export default class MessageReceiver
|
||||||
envelope: UnsealedEnvelope,
|
envelope: UnsealedEnvelope,
|
||||||
ciphertext: Uint8Array,
|
ciphertext: Uint8Array,
|
||||||
uuidKind: UUIDKind
|
uuidKind: UUIDKind
|
||||||
): Promise<Uint8Array | undefined> {
|
): Promise<InnerDecryptResultType | undefined> {
|
||||||
try {
|
try {
|
||||||
return await this.innerDecrypt(stores, envelope, ciphertext, uuidKind);
|
return await this.innerDecrypt(stores, envelope, ciphertext, uuidKind);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -161,6 +161,18 @@ export class DecryptionErrorEvent extends ConfirmableEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InvalidPlaintextEventData = Readonly<{
|
||||||
|
senderDevice: number;
|
||||||
|
senderUuid: string;
|
||||||
|
timestamp: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class InvalidPlaintextEvent extends Event {
|
||||||
|
constructor(public readonly data: InvalidPlaintextEventData) {
|
||||||
|
super('invalid-plaintext');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type RetryRequestEventData = Readonly<{
|
export type RetryRequestEventData = Readonly<{
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
ratchetKey?: PublicKey;
|
ratchetKey?: PublicKey;
|
||||||
|
|
|
@ -29,6 +29,7 @@ import type { ConversationModel } from '../models/conversations';
|
||||||
import type {
|
import type {
|
||||||
DecryptionErrorEvent,
|
DecryptionErrorEvent,
|
||||||
DecryptionErrorEventData,
|
DecryptionErrorEventData,
|
||||||
|
InvalidPlaintextEvent,
|
||||||
RetryRequestEvent,
|
RetryRequestEvent,
|
||||||
RetryRequestEventData,
|
RetryRequestEventData,
|
||||||
} from '../textsecure/messageReceiverEvents';
|
} from '../textsecure/messageReceiverEvents';
|
||||||
|
@ -205,6 +206,23 @@ function maybeShowDecryptionToast(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function onInvalidPlaintextMessage({
|
||||||
|
data,
|
||||||
|
}: InvalidPlaintextEvent): void {
|
||||||
|
const { senderUuid, senderDevice, timestamp } = data;
|
||||||
|
const logId = `${senderUuid}.${senderDevice} ${timestamp}`;
|
||||||
|
|
||||||
|
log.info(`onInvalidPlaintextMessage/${logId}: Starting...`);
|
||||||
|
|
||||||
|
const conversation = window.ConversationController.getOrCreate(
|
||||||
|
senderUuid,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
|
||||||
|
const name = conversation.getTitle();
|
||||||
|
maybeShowDecryptionToast(logId, name, senderDevice);
|
||||||
|
}
|
||||||
|
|
||||||
export async function onDecryptionError(
|
export async function onDecryptionError(
|
||||||
event: DecryptionErrorEvent
|
event: DecryptionErrorEvent
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue