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,
|
||||
FetchLatestEvent,
|
||||
GroupEvent,
|
||||
InvalidPlaintextEvent,
|
||||
KeysEvent,
|
||||
MessageEvent,
|
||||
MessageEventData,
|
||||
|
@ -140,7 +141,11 @@ import * as Conversation from './types/Conversation';
|
|||
import * as Stickers from './types/Stickers';
|
||||
import * as Errors from './types/errors';
|
||||
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 { createIPCEvents } from './util/createIPCEvents';
|
||||
import { RemoveAllConfiguration } from './types/RemoveAllConfiguration';
|
||||
|
@ -390,6 +395,14 @@ export async function startApp(): Promise<void> {
|
|||
drop(onDecryptionErrorQueue.add(() => onDecryptionError(event)));
|
||||
})
|
||||
);
|
||||
messageReceiver.addEventListener(
|
||||
'invalid-plaintext',
|
||||
queuedEventListener((event: InvalidPlaintextEvent): void => {
|
||||
drop(
|
||||
onDecryptionErrorQueue.add(() => onInvalidPlaintextMessage(event))
|
||||
);
|
||||
})
|
||||
);
|
||||
messageReceiver.addEventListener(
|
||||
'retry-request',
|
||||
queuedEventListener((event: RetryRequestEvent): void => {
|
||||
|
|
|
@ -96,6 +96,7 @@ import {
|
|||
DecryptionErrorEvent,
|
||||
SentEvent,
|
||||
ProfileKeyUpdateEvent,
|
||||
InvalidPlaintextEvent,
|
||||
MessageEvent,
|
||||
RetryRequestEvent,
|
||||
ReadEvent,
|
||||
|
@ -160,6 +161,11 @@ type DecryptSealedSenderResult = Readonly<{
|
|||
unsealedPlaintext?: SealedSenderDecryptionResult;
|
||||
}>;
|
||||
|
||||
type InnerDecryptResultType = Readonly<{
|
||||
plaintext: Uint8Array;
|
||||
wasEncrypted: boolean;
|
||||
}>;
|
||||
|
||||
type CacheAddItemType = {
|
||||
envelope: ProcessedEnvelope;
|
||||
data: UnprocessedType;
|
||||
|
@ -533,6 +539,11 @@ export default class MessageReceiver
|
|||
handler: (ev: DecryptionErrorEvent) => void
|
||||
): void;
|
||||
|
||||
public override addEventListener(
|
||||
name: 'invalid-plaintext',
|
||||
handler: (ev: InvalidPlaintextEvent) => void
|
||||
): void;
|
||||
|
||||
public override addEventListener(
|
||||
name: 'sent',
|
||||
handler: (ev: SentEvent) => void
|
||||
|
@ -1395,18 +1406,20 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
log.info(logId);
|
||||
const plaintext = await this.decrypt(
|
||||
const decryptResult = await this.decrypt(
|
||||
stores,
|
||||
envelope,
|
||||
ciphertext,
|
||||
uuidKind
|
||||
);
|
||||
|
||||
if (!plaintext) {
|
||||
if (!decryptResult) {
|
||||
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
|
||||
// sender key to decrypt the next message in the queue!
|
||||
let isGroupV2 = false;
|
||||
|
@ -1414,6 +1427,32 @@ export default class MessageReceiver
|
|||
let inProgressMessageType = '';
|
||||
try {
|
||||
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 =
|
||||
Boolean(content.dataMessage?.groupV2) ||
|
||||
|
@ -1742,7 +1781,7 @@ export default class MessageReceiver
|
|||
envelope: UnsealedEnvelope,
|
||||
ciphertext: Uint8Array,
|
||||
uuidKind: UUIDKind
|
||||
): Promise<Uint8Array | undefined> {
|
||||
): Promise<InnerDecryptResultType | undefined> {
|
||||
const { sessionStore, identityKeyStore, zone } = stores;
|
||||
|
||||
const logId = getEnvelopeId(envelope);
|
||||
|
@ -1784,7 +1823,10 @@ export default class MessageReceiver
|
|||
const buffer = Buffer.from(ciphertext);
|
||||
const plaintextContent = PlaintextContent.deserialize(buffer);
|
||||
|
||||
return this.unpad(plaintextContent.body());
|
||||
return {
|
||||
plaintext: this.unpad(plaintextContent.body()),
|
||||
wasEncrypted: false,
|
||||
};
|
||||
}
|
||||
if (envelope.type === envelopeTypeEnum.CIPHERTEXT) {
|
||||
log.info(`decrypt/${logId}: ciphertext message`);
|
||||
|
@ -1813,7 +1855,7 @@ export default class MessageReceiver
|
|||
),
|
||||
zone
|
||||
);
|
||||
return plaintext;
|
||||
return { plaintext, wasEncrypted: true };
|
||||
}
|
||||
if (envelope.type === envelopeTypeEnum.PREKEY_BUNDLE) {
|
||||
log.info(`decrypt/${logId}: prekey message`);
|
||||
|
@ -1846,7 +1888,7 @@ export default class MessageReceiver
|
|||
),
|
||||
zone
|
||||
);
|
||||
return plaintext;
|
||||
return { plaintext, wasEncrypted: true };
|
||||
}
|
||||
if (envelope.type === envelopeTypeEnum.UNIDENTIFIED_SENDER) {
|
||||
log.info(`decrypt/${logId}: unidentified message`);
|
||||
|
@ -1857,7 +1899,7 @@ export default class MessageReceiver
|
|||
);
|
||||
|
||||
if (plaintext) {
|
||||
return this.unpad(plaintext);
|
||||
return { plaintext: this.unpad(plaintext), wasEncrypted: false };
|
||||
}
|
||||
|
||||
if (unsealedPlaintext) {
|
||||
|
@ -1871,7 +1913,7 @@ export default class MessageReceiver
|
|||
|
||||
// Return just the content because that matches the signature of the other
|
||||
// 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');
|
||||
|
@ -1884,7 +1926,7 @@ export default class MessageReceiver
|
|||
envelope: UnsealedEnvelope,
|
||||
ciphertext: Uint8Array,
|
||||
uuidKind: UUIDKind
|
||||
): Promise<Uint8Array | undefined> {
|
||||
): Promise<InnerDecryptResultType | undefined> {
|
||||
try {
|
||||
return await this.innerDecrypt(stores, envelope, ciphertext, uuidKind);
|
||||
} 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<{
|
||||
groupId?: string;
|
||||
ratchetKey?: PublicKey;
|
||||
|
|
|
@ -29,6 +29,7 @@ import type { ConversationModel } from '../models/conversations';
|
|||
import type {
|
||||
DecryptionErrorEvent,
|
||||
DecryptionErrorEventData,
|
||||
InvalidPlaintextEvent,
|
||||
RetryRequestEvent,
|
||||
RetryRequestEventData,
|
||||
} 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(
|
||||
event: DecryptionErrorEvent
|
||||
): Promise<void> {
|
||||
|
|
Loading…
Add table
Reference in a new issue