Unseal envelope in a separate step for better logs
This commit is contained in:
parent
907e1d32ec
commit
21ffb7c054
3 changed files with 110 additions and 73 deletions
|
@ -44,4 +44,12 @@ describe('createTaskWithTimeout', () => {
|
||||||
});
|
});
|
||||||
await assert.isRejected(taskWithTimeout(), 'Task is throwing!');
|
await assert.isRejected(taskWithTimeout(), 'Task is throwing!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('passes arguments to the underlying function', async () => {
|
||||||
|
const task = (arg: string) => Promise.resolve(arg);
|
||||||
|
const taskWithTimeout = createTaskWithTimeout(task, 'test');
|
||||||
|
|
||||||
|
const result = await taskWithTimeout('hi!');
|
||||||
|
assert.strictEqual(result, 'hi!');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -919,21 +919,27 @@ export default class MessageReceiver
|
||||||
stores: LockedStores,
|
stores: LockedStores,
|
||||||
envelope: ProcessedEnvelope
|
envelope: ProcessedEnvelope
|
||||||
): Promise<DecryptResult> {
|
): Promise<DecryptResult> {
|
||||||
const id = this.getEnvelopeId(envelope);
|
let logId = this.getEnvelopeId(envelope);
|
||||||
window.log.info('queueing envelope', id);
|
window.log.info('queueing envelope', logId);
|
||||||
|
|
||||||
const task = this.decryptEnvelope.bind(this, stores, envelope);
|
const task = createTaskWithTimeout(async (): Promise<DecryptResult> => {
|
||||||
const taskWithTimeout = createTaskWithTimeout(
|
const unsealedEnvelope = await this.unsealEnvelope(stores, envelope);
|
||||||
task,
|
if (!unsealedEnvelope) {
|
||||||
`queueEncryptedEnvelope ${id}`
|
// Envelope was dropped or sender is blocked
|
||||||
);
|
return { envelope, plaintext: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
logId = this.getEnvelopeId(unsealedEnvelope);
|
||||||
|
|
||||||
|
return this.decryptEnvelope(stores, unsealedEnvelope);
|
||||||
|
}, `MessageReceiver: unseal and decrypt ${logId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.addToQueue(taskWithTimeout, TaskType.Encrypted);
|
return await this.addToQueue(task, TaskType.Encrypted);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const args = [
|
const args = [
|
||||||
'queueEncryptedEnvelope error handling envelope',
|
'queueEncryptedEnvelope error handling envelope',
|
||||||
this.getEnvelopeId(envelope),
|
logId,
|
||||||
':',
|
':',
|
||||||
Errors.toLogFormat(error),
|
Errors.toLogFormat(error),
|
||||||
];
|
];
|
||||||
|
@ -993,15 +999,86 @@ export default class MessageReceiver
|
||||||
throw new Error('Received message with no content and no legacyMessage');
|
throw new Error('Received message with no content and no legacyMessage');
|
||||||
}
|
}
|
||||||
|
|
||||||
private async decryptEnvelope(
|
private async unsealEnvelope(
|
||||||
stores: LockedStores,
|
stores: LockedStores,
|
||||||
initialEnvelope: ProcessedEnvelope
|
envelope: ProcessedEnvelope
|
||||||
): Promise<DecryptResult> {
|
): Promise<UnsealedEnvelope | undefined> {
|
||||||
let envelope: UnsealedEnvelope = initialEnvelope;
|
const logId = this.getEnvelopeId(envelope);
|
||||||
let logId = this.getEnvelopeId(envelope);
|
|
||||||
|
|
||||||
if (this.stoppingProcessing) {
|
if (this.stoppingProcessing) {
|
||||||
window.log.info(`MessageReceiver.decryptEnvelope(${logId}): dropping`);
|
window.log.info(`MessageReceiver.unsealEnvelope(${logId}): dropping`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (envelope.type !== Proto.Envelope.Type.UNIDENTIFIED_SENDER) {
|
||||||
|
return envelope;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphertext = envelope.content || envelope.legacyMessage;
|
||||||
|
if (!ciphertext) {
|
||||||
|
this.removeFromCache(envelope);
|
||||||
|
throw new Error('Received message with no content and no legacyMessage');
|
||||||
|
}
|
||||||
|
|
||||||
|
window.log.info(
|
||||||
|
`MessageReceiver.unsealEnvelope(${logId}): unidentified message`
|
||||||
|
);
|
||||||
|
const messageContent = await sealedSenderDecryptToUsmc(
|
||||||
|
Buffer.from(ciphertext),
|
||||||
|
stores.identityKeyStore
|
||||||
|
);
|
||||||
|
|
||||||
|
// Here we take this sender information and attach it back to the envelope
|
||||||
|
// to make the rest of the app work properly.
|
||||||
|
const certificate = messageContent.senderCertificate();
|
||||||
|
|
||||||
|
const originalSource = envelope.source;
|
||||||
|
const originalSourceUuid = envelope.sourceUuid;
|
||||||
|
|
||||||
|
const newEnvelope: UnsealedEnvelope = {
|
||||||
|
...envelope,
|
||||||
|
|
||||||
|
// Overwrite Envelope fields
|
||||||
|
source: dropNull(certificate.senderE164()),
|
||||||
|
sourceUuid: normalizeUuid(
|
||||||
|
certificate.senderUuid(),
|
||||||
|
'MessageReceiver.unsealEnvelope.UNIDENTIFIED_SENDER.sourceUuid'
|
||||||
|
),
|
||||||
|
sourceDevice: certificate.senderDeviceId(),
|
||||||
|
|
||||||
|
// UnsealedEnvelope-only fields
|
||||||
|
unidentifiedDeliveryReceived: !(originalSource || originalSourceUuid),
|
||||||
|
contentHint: messageContent.contentHint(),
|
||||||
|
groupId: messageContent.groupId()?.toString('base64'),
|
||||||
|
usmc: messageContent,
|
||||||
|
certificate,
|
||||||
|
unsealedContent: messageContent,
|
||||||
|
};
|
||||||
|
const newLogId = this.getEnvelopeId(newEnvelope);
|
||||||
|
|
||||||
|
const validationResult = await this.validateUnsealedEnvelope(newEnvelope);
|
||||||
|
if (validationResult && validationResult.isBlocked) {
|
||||||
|
this.removeFromCache(envelope);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.log.info(
|
||||||
|
`MessageReceiver.unsealEnvelope(${logId}): unwrapped into ${newLogId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return newEnvelope;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async decryptEnvelope(
|
||||||
|
stores: LockedStores,
|
||||||
|
envelope: UnsealedEnvelope
|
||||||
|
): Promise<DecryptResult> {
|
||||||
|
const logId = this.getEnvelopeId(envelope);
|
||||||
|
|
||||||
|
if (this.stoppingProcessing) {
|
||||||
|
window.log.info(
|
||||||
|
`MessageReceiver.decryptEnvelope(${logId}): dropping unsealed`
|
||||||
|
);
|
||||||
return { plaintext: undefined, envelope };
|
return { plaintext: undefined, envelope };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1019,58 +1096,10 @@ export default class MessageReceiver
|
||||||
isLegacy = true;
|
isLegacy = true;
|
||||||
} else {
|
} else {
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
throw new Error('Received message with no content and no legacyMessage');
|
strictAssert(
|
||||||
}
|
false,
|
||||||
|
'Contentless envelope should be handled by unsealEnvelope'
|
||||||
if (envelope.type === Proto.Envelope.Type.UNIDENTIFIED_SENDER) {
|
|
||||||
window.log.info(
|
|
||||||
`MessageReceiver.decryptEnvelope(${logId}): unidentified message`
|
|
||||||
);
|
);
|
||||||
const messageContent = await sealedSenderDecryptToUsmc(
|
|
||||||
Buffer.from(ciphertext),
|
|
||||||
stores.identityKeyStore
|
|
||||||
);
|
|
||||||
|
|
||||||
// Here we take this sender information and attach it back to the envelope
|
|
||||||
// to make the rest of the app work properly.
|
|
||||||
const certificate = messageContent.senderCertificate();
|
|
||||||
|
|
||||||
const originalSource = envelope.source;
|
|
||||||
const originalSourceUuid = envelope.sourceUuid;
|
|
||||||
|
|
||||||
const newEnvelope: UnsealedEnvelope = {
|
|
||||||
...envelope,
|
|
||||||
|
|
||||||
// Overwrite Envelope fields
|
|
||||||
source: dropNull(certificate.senderE164()),
|
|
||||||
sourceUuid: normalizeUuid(
|
|
||||||
certificate.senderUuid(),
|
|
||||||
'MessageReceiver.decryptEnvelope.UNIDENTIFIED_SENDER.sourceUuid'
|
|
||||||
),
|
|
||||||
sourceDevice: certificate.senderDeviceId(),
|
|
||||||
|
|
||||||
// UnsealedEnvelope-only fields
|
|
||||||
unidentifiedDeliveryReceived: !(originalSource || originalSourceUuid),
|
|
||||||
contentHint: messageContent.contentHint(),
|
|
||||||
groupId: messageContent.groupId()?.toString('base64'),
|
|
||||||
usmc: messageContent,
|
|
||||||
certificate,
|
|
||||||
unsealedContent: messageContent,
|
|
||||||
};
|
|
||||||
const newLogId = this.getEnvelopeId(newEnvelope);
|
|
||||||
|
|
||||||
const validationResult = await this.validateUnsealedEnvelope(newEnvelope);
|
|
||||||
if (validationResult && validationResult.isBlocked) {
|
|
||||||
this.removeFromCache(envelope);
|
|
||||||
return { plaintext: undefined, envelope };
|
|
||||||
}
|
|
||||||
|
|
||||||
window.log.info(
|
|
||||||
`MessageReceiver.decryptEnvelope(${logId}): unwrapped into ${newLogId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
envelope = newEnvelope;
|
|
||||||
logId = newLogId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.log.info(
|
window.log.info(
|
||||||
|
@ -1079,7 +1108,7 @@ export default class MessageReceiver
|
||||||
const plaintext = await this.decrypt(stores, envelope, ciphertext);
|
const plaintext = await this.decrypt(stores, envelope, ciphertext);
|
||||||
|
|
||||||
if (!plaintext) {
|
if (!plaintext) {
|
||||||
window.log.warn('MessageReceiver.decrryptEnvelope: plaintext was falsey');
|
window.log.warn('MessageReceiver.decryptEnvelope: plaintext was falsey');
|
||||||
return { plaintext, envelope };
|
return { plaintext, envelope };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1105,7 +1134,7 @@ export default class MessageReceiver
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'MessageReceiver.decrryptEnvelope: Failed to process sender ' +
|
'MessageReceiver.decryptEnvelope: Failed to process sender ' +
|
||||||
`key distribution message: ${Errors.toLogFormat(error)}`
|
`key distribution message: ${Errors.toLogFormat(error)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
export default function createTaskWithTimeout<T>(
|
export default function createTaskWithTimeout<T, Args extends Array<unknown>>(
|
||||||
task: () => Promise<T>,
|
task: (...args: Args) => Promise<T>,
|
||||||
id: string,
|
id: string,
|
||||||
options: { timeout?: number } = {}
|
options: { timeout?: number } = {}
|
||||||
): () => Promise<T> {
|
): (...args: Args) => Promise<T> {
|
||||||
const timeout = options.timeout || 1000 * 60 * 2; // two minutes
|
const timeout = options.timeout || 1000 * 60 * 2; // two minutes
|
||||||
|
|
||||||
const errorForStack = new Error('for stack');
|
const errorForStack = new Error('for stack');
|
||||||
|
|
||||||
return async () =>
|
return async (...args: Args) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
let complete = false;
|
let complete = false;
|
||||||
let timer: NodeJS.Timeout | null = setTimeout(() => {
|
let timer: NodeJS.Timeout | null = setTimeout(() => {
|
||||||
|
@ -58,7 +58,7 @@ export default function createTaskWithTimeout<T>(
|
||||||
|
|
||||||
let promise;
|
let promise;
|
||||||
try {
|
try {
|
||||||
promise = task();
|
promise = task(...args);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
clearTimer();
|
clearTimer();
|
||||||
throw error;
|
throw error;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue