MessageReceiver: Unwrap envelopes earlier in the processing chain

This commit is contained in:
Fedor Indutny 2021-07-27 12:55:39 -07:00 committed by GitHub
parent eceae64d41
commit 85004699f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -22,6 +22,7 @@ import {
SealedSenderDecryptionResult, SealedSenderDecryptionResult,
sealedSenderDecryptMessage, sealedSenderDecryptMessage,
sealedSenderDecryptToUsmc, sealedSenderDecryptToUsmc,
SenderCertificate,
SenderKeyDistributionMessage, SenderKeyDistributionMessage,
signalDecrypt, signalDecrypt,
signalDecryptPreKey, signalDecryptPreKey,
@ -38,7 +39,7 @@ import {
} from '../LibSignalStores'; } from '../LibSignalStores';
import { verifySignature } from '../Curve'; import { verifySignature } from '../Curve';
import { BackOff, FIBONACCI_TIMEOUTS } from '../util/BackOff'; import { BackOff, FIBONACCI_TIMEOUTS } from '../util/BackOff';
import { assert, strictAssert } from '../util/assert'; import { strictAssert } from '../util/assert';
import { BatcherType, createBatcher } from '../util/batcher'; import { BatcherType, createBatcher } from '../util/batcher';
import { dropNull } from '../util/dropNull'; import { dropNull } from '../util/dropNull';
import { normalizeUuid } from '../util/normalizeUuid'; import { normalizeUuid } from '../util/normalizeUuid';
@ -60,6 +61,7 @@ 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 { DownloadedAttachmentType } from '../types/Attachment'; import { DownloadedAttachmentType } from '../types/Attachment';
import * as Errors from '../types/errors';
import * as MIME from '../types/MIME'; import * as MIME from '../types/MIME';
import { SocketStatus } from '../types/SocketStatus'; import { SocketStatus } from '../types/SocketStatus';
@ -110,32 +112,26 @@ const GROUPV1_ID_LENGTH = 16;
const GROUPV2_ID_LENGTH = 32; const GROUPV2_ID_LENGTH = 32;
const RETRY_TIMEOUT = 2 * 60 * 1000; const RETRY_TIMEOUT = 2 * 60 * 1000;
type DecryptedEnvelope = Readonly< type UnsealedEnvelope = Readonly<
ProcessedEnvelope & { ProcessedEnvelope & {
unidentifiedDeliveryReceived?: boolean; unidentifiedDeliveryReceived?: boolean;
contentHint?: number; contentHint?: number;
groupId?: string; groupId?: string;
usmc?: UnidentifiedSenderMessageContent; usmc?: UnidentifiedSenderMessageContent;
certificate?: SenderCertificate;
unsealedContent?: UnidentifiedSenderMessageContent;
} }
>; >;
type DecryptResult = Readonly<{ type DecryptResult = Readonly<{
envelope: DecryptedEnvelope; envelope: UnsealedEnvelope;
plaintext?: Uint8Array; plaintext?: Uint8Array;
}>; }>;
type DecryptSealedSenderResult = Readonly< type DecryptSealedSenderResult = Readonly<{
DecryptResult & { plaintext?: Uint8Array;
unsealedPlaintext?: SealedSenderDecryptionResult; unsealedPlaintext?: SealedSenderDecryptionResult;
isBlocked?: boolean; }>;
}
>;
type InnerDecryptResult = Readonly<
DecryptResult & {
isBlocked?: boolean;
}
>;
type CacheAddItemType = { type CacheAddItemType = {
envelope: ProcessedEnvelope; envelope: ProcessedEnvelope;
@ -694,7 +690,7 @@ export default class MessageReceiver extends EventTarget {
request.respond(500, 'Bad encrypted websocket message'); request.respond(500, 'Bad encrypted websocket message');
window.log.error( window.log.error(
'Error handling incoming message:', 'Error handling incoming message:',
e && e.stack ? e.stack : e Errors.toLogFormat(e)
); );
await this.dispatchAndWait(new ErrorEvent(e)); await this.dispatchAndWait(new ErrorEvent(e));
} }
@ -889,7 +885,7 @@ export default class MessageReceiver extends EventTarget {
'queueCached error handling item', 'queueCached error handling item',
item.id, item.id,
'removing it. Error:', 'removing it. Error:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
try { try {
@ -900,7 +896,7 @@ export default class MessageReceiver extends EventTarget {
'queueCached error deleting item', 'queueCached error deleting item',
item.id, item.id,
'Error:', 'Error:',
deleteError && deleteError.stack ? deleteError.stack : deleteError Errors.toLogFormat(deleteError)
); );
} }
} }
@ -968,7 +964,7 @@ export default class MessageReceiver extends EventTarget {
} catch (error) { } catch (error) {
window.log.error( window.log.error(
'getAllFromCache error updating item after load:', 'getAllFromCache error updating item after load:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
@ -986,7 +982,7 @@ export default class MessageReceiver extends EventTarget {
Readonly<{ Readonly<{
plaintext: Uint8Array; plaintext: Uint8Array;
data: UnprocessedType; data: UnprocessedType;
envelope: DecryptedEnvelope; envelope: UnsealedEnvelope;
}> }>
> = []; > = [];
@ -1029,7 +1025,7 @@ export default class MessageReceiver extends EventTarget {
failed.push(data); failed.push(data);
window.log.error( window.log.error(
'decryptAndCache error when processing the envelope', 'decryptAndCache error when processing the envelope',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
}) })
@ -1079,7 +1075,7 @@ export default class MessageReceiver extends EventTarget {
} catch (error) { } catch (error) {
window.log.error( window.log.error(
'decryptAndCache error trying to add messages to cache:', 'decryptAndCache error trying to add messages to cache:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
items.forEach(item => { items.forEach(item => {
@ -1095,7 +1091,7 @@ export default class MessageReceiver extends EventTarget {
} catch (error) { } catch (error) {
window.log.error( window.log.error(
'decryptAndCache error when processing decrypted envelope', 'decryptAndCache error when processing decrypted envelope',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
}) })
@ -1137,7 +1133,7 @@ export default class MessageReceiver extends EventTarget {
} }
private async queueDecryptedEnvelope( private async queueDecryptedEnvelope(
envelope: DecryptedEnvelope, envelope: UnsealedEnvelope,
plaintext: Uint8Array plaintext: Uint8Array
): Promise<void> { ): Promise<void> {
const id = this.getEnvelopeId(envelope); const id = this.getEnvelopeId(envelope);
@ -1154,8 +1150,7 @@ export default class MessageReceiver extends EventTarget {
} catch (error) { } catch (error) {
window.log.error( window.log.error(
`queueDecryptedEnvelope error handling envelope ${id}:`, `queueDecryptedEnvelope error handling envelope ${id}:`,
error && error.extra ? JSON.stringify(error.extra) : '', Errors.toLogFormat(error)
error && error.stack ? error.stack : error
); );
} }
} }
@ -1178,10 +1173,9 @@ export default class MessageReceiver extends EventTarget {
} catch (error) { } catch (error) {
const args = [ const args = [
'queueEncryptedEnvelope error handling envelope', 'queueEncryptedEnvelope error handling envelope',
this.getEnvelopeId(error.envelope || envelope), this.getEnvelopeId(envelope),
':', ':',
error && error.extra ? JSON.stringify(error.extra) : '', Errors.toLogFormat(error),
error && error.stack ? error.stack : error,
]; ];
if (error.warn) { if (error.warn) {
window.log.warn(...args); window.log.warn(...args);
@ -1215,7 +1209,7 @@ export default class MessageReceiver extends EventTarget {
// Called after `decryptEnvelope` decrypted the message. // Called after `decryptEnvelope` decrypted the message.
private async handleDecryptedEnvelope( private async handleDecryptedEnvelope(
envelope: DecryptedEnvelope, envelope: UnsealedEnvelope,
plaintext: Uint8Array plaintext: Uint8Array
): Promise<void> { ): Promise<void> {
if (this.stoppingProcessing) { if (this.stoppingProcessing) {
@ -1241,9 +1235,13 @@ export default class MessageReceiver extends EventTarget {
private async decryptEnvelope( private async decryptEnvelope(
stores: LockedStores, stores: LockedStores,
envelope: ProcessedEnvelope initialEnvelope: ProcessedEnvelope
): Promise<DecryptResult> { ): Promise<DecryptResult> {
let envelope: UnsealedEnvelope = initialEnvelope;
let logId = this.getEnvelopeId(envelope);
if (this.stoppingProcessing) { if (this.stoppingProcessing) {
window.log.info(`MessageReceiver.decryptEnvelope(${logId}): dropping`);
return { plaintext: undefined, envelope }; return { plaintext: undefined, envelope };
} }
@ -1252,17 +1250,180 @@ export default class MessageReceiver extends EventTarget {
return { plaintext: undefined, envelope }; return { plaintext: undefined, envelope };
} }
let ciphertext: Uint8Array;
let isLegacy = false;
if (envelope.content) { if (envelope.content) {
return this.decryptContentMessage(stores, envelope); ciphertext = envelope.content;
} } else if (envelope.legacyMessage) {
if (envelope.legacyMessage) { ciphertext = envelope.legacyMessage;
return this.decryptLegacyMessage(stores, envelope); isLegacy = true;
} } else {
this.removeFromCache(envelope); this.removeFromCache(envelope);
throw new Error('Received message with no content and no legacyMessage'); throw new Error('Received message with no content and no legacyMessage');
} }
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(
`MessageReceiver.decryptEnvelope(${logId})${isLegacy ? ' (legacy)' : ''}`
);
const plaintext = await this.decrypt(stores, envelope, ciphertext);
if (!plaintext) {
window.log.warn('MessageReceiver.decrryptEnvelope: plaintext was falsey');
return { plaintext, envelope };
}
// Legacy envelopes do not carry senderKeyDistributionMessage
if (isLegacy) {
return { plaintext, envelope };
}
// 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!
try {
const content = Proto.Content.decode(plaintext);
if (
content.senderKeyDistributionMessage &&
Bytes.isNotEmpty(content.senderKeyDistributionMessage)
) {
await this.handleSenderKeyDistributionMessage(
stores,
envelope,
content.senderKeyDistributionMessage
);
}
} catch (error) {
window.log.error(
'MessageReceiver.decrryptEnvelope: Failed to process sender ' +
`key distribution message: ${Errors.toLogFormat(error)}`
);
}
return { plaintext, envelope };
}
private async validateUnsealedEnvelope(
envelope: UnsealedEnvelope
): Promise<{ isBlocked: true } | void> {
const { unsealedContent: messageContent, certificate } = envelope;
strictAssert(
messageContent !== undefined,
'Missing message content for sealed sender message'
);
strictAssert(
certificate !== undefined,
'Missing sender certificate for sealed sender message'
);
if (
(envelope.source && this.isBlocked(envelope.source)) ||
(envelope.sourceUuid && this.isUuidBlocked(envelope.sourceUuid))
) {
window.log.info(
'MessageReceiver.validateUnsealedEnvelope: Dropping blocked message ' +
'after partial sealed sender decryption'
);
return { isBlocked: true };
}
if (!envelope.serverTimestamp) {
throw new Error(
'MessageReceiver.decryptSealedSender: ' +
'Sealed sender message was missing serverTimestamp'
);
}
const serverCertificate = certificate.serverCertificate();
if (
!verifySignature(
typedArrayToArrayBuffer(this.serverTrustRoot),
typedArrayToArrayBuffer(serverCertificate.certificateData()),
typedArrayToArrayBuffer(serverCertificate.signature())
)
) {
throw new Error(
'MessageReceiver.validateUnsealedEnvelope: ' +
'Server certificate trust root validation failed'
);
}
if (
!verifySignature(
typedArrayToArrayBuffer(serverCertificate.key().serialize()),
typedArrayToArrayBuffer(certificate.certificate()),
typedArrayToArrayBuffer(certificate.signature())
)
) {
throw new Error(
'MessageReceiver.validateUnsealedEnvelope: ' +
'Server certificate server signature validation failed'
);
}
const logId = this.getEnvelopeId(envelope);
if (envelope.serverTimestamp > certificate.expiration()) {
throw new Error(
`MessageReceiver.validateUnsealedEnvelope: ' +
'Sender certificate is expired for envelope ${logId}`
);
}
return undefined;
}
private async onDeliveryReceipt(envelope: ProcessedEnvelope): Promise<void> { private async onDeliveryReceipt(envelope: ProcessedEnvelope): Promise<void> {
await this.dispatchAndWait( await this.dispatchAndWait(
new DeliveryEvent( new DeliveryEvent(
@ -1292,14 +1453,9 @@ export default class MessageReceiver extends EventTarget {
private async decryptSealedSender( private async decryptSealedSender(
{ sessionStore, identityKeyStore, zone }: LockedStores, { sessionStore, identityKeyStore, zone }: LockedStores,
envelope: ProcessedEnvelope, envelope: UnsealedEnvelope,
ciphertext: Uint8Array ciphertext: Uint8Array
): Promise<{ ): Promise<DecryptSealedSenderResult> {
error?: Error;
result: DecryptSealedSenderResult;
}> {
const buffer = Buffer.from(ciphertext);
const localE164 = window.textsecure.storage.user.getNumber(); const localE164 = window.textsecure.storage.user.getNumber();
const localUuid = window.textsecure.storage.user.getUuid(); const localUuid = window.textsecure.storage.user.getUuid();
const localDeviceId = parseIntOrThrow( const localDeviceId = parseIntOrThrow(
@ -1313,86 +1469,17 @@ export default class MessageReceiver extends EventTarget {
); );
} }
const messageContent: UnidentifiedSenderMessageContent = await sealedSenderDecryptToUsmc( const logId = this.getEnvelopeId(envelope);
buffer,
identityKeyStore const { unsealedContent: messageContent, certificate } = envelope;
strictAssert(
messageContent !== undefined,
'Missing message content for sealed sender message'
); );
strictAssert(
// Here we take this sender information and attach it back to the envelope certificate !== undefined,
// to make the rest of the app work properly. 'Missing sender certificate for sealed sender message'
const certificate = messageContent.senderCertificate();
const originalSource = envelope.source;
const originalSourceUuid = envelope.sourceUuid;
const newEnvelope: DecryptedEnvelope = {
...envelope,
source: dropNull(certificate.senderE164()),
sourceUuid: normalizeUuid(
certificate.senderUuid(),
'MessageReceiver.decryptSealedSender.UNIDENTIFIED_SENDER.sourceUuid'
),
sourceDevice: certificate.senderDeviceId(),
unidentifiedDeliveryReceived: !(originalSource || originalSourceUuid),
contentHint: messageContent.contentHint(),
groupId: messageContent.groupId()?.toString('base64'),
usmc: messageContent,
};
if (
(newEnvelope.source && this.isBlocked(newEnvelope.source)) ||
(newEnvelope.sourceUuid && this.isUuidBlocked(newEnvelope.sourceUuid))
) {
window.log.info(
'MessageReceiver.decryptSealedSender: Dropping blocked message after ' +
'partial sealed sender decryption'
); );
return {
result: { isBlocked: true, envelope: newEnvelope },
};
}
if (!newEnvelope.serverTimestamp) {
throw new Error(
'MessageReceiver.decryptSealedSender: ' +
'Sealed sender message was missing serverTimestamp'
);
}
const serverCertificate = certificate.serverCertificate();
if (
!verifySignature(
typedArrayToArrayBuffer(this.serverTrustRoot),
typedArrayToArrayBuffer(serverCertificate.certificateData()),
typedArrayToArrayBuffer(serverCertificate.signature())
)
) {
throw new Error(
'MessageReceiver.decryptSealedSender: Server certificate trust root validation failed'
);
}
if (
!verifySignature(
typedArrayToArrayBuffer(serverCertificate.key().serialize()),
typedArrayToArrayBuffer(certificate.certificate()),
typedArrayToArrayBuffer(certificate.signature())
)
) {
throw new Error(
'MessageReceiver.decryptSealedSender: Server certificate server signature validation failed'
);
}
const unidentifiedLogId = this.getEnvelopeId(newEnvelope);
if (newEnvelope.serverTimestamp > certificate.expiration()) {
throw new Error(
`MessageReceiver.decryptSealedSender: Sender certificate is expired for envelope ${unidentifiedLogId}`
);
}
const unidentifiedSenderTypeEnum = const unidentifiedSenderTypeEnum =
Proto.UnidentifiedSenderMessage.Message.Type; Proto.UnidentifiedSenderMessage.Message.Type;
@ -1401,17 +1488,15 @@ export default class MessageReceiver extends EventTarget {
messageContent.msgType() === unidentifiedSenderTypeEnum.PLAINTEXT_CONTENT messageContent.msgType() === unidentifiedSenderTypeEnum.PLAINTEXT_CONTENT
) { ) {
window.log.info( window.log.info(
`decrypt/${unidentifiedLogId}: unidentified message/plaintext contents` `MessageReceiver.decryptSealedSender(${logId}): ` +
'unidentified message/plaintext contents'
); );
const plaintextContent = PlaintextContent.deserialize( const plaintextContent = PlaintextContent.deserialize(
messageContent.contents() messageContent.contents()
); );
return { return {
result: {
plaintext: plaintextContent.body(), plaintext: plaintextContent.body(),
envelope: newEnvelope,
},
}; };
} }
@ -1419,7 +1504,8 @@ export default class MessageReceiver extends EventTarget {
messageContent.msgType() === unidentifiedSenderTypeEnum.SENDERKEY_MESSAGE messageContent.msgType() === unidentifiedSenderTypeEnum.SENDERKEY_MESSAGE
) { ) {
window.log.info( window.log.info(
`decrypt/${unidentifiedLogId}: unidentified message/sender key contents` `MessageReceiver.decryptSealedSender(${logId}): ` +
'unidentified message/sender key contents'
); );
const sealedSenderIdentifier = certificate.senderUuid(); const sealedSenderIdentifier = certificate.senderUuid();
const sealedSenderSourceDevice = certificate.senderDeviceId(); const sealedSenderSourceDevice = certificate.senderDeviceId();
@ -1427,7 +1513,6 @@ export default class MessageReceiver extends EventTarget {
const address = `${sealedSenderIdentifier}.${sealedSenderSourceDevice}`; const address = `${sealedSenderIdentifier}.${sealedSenderSourceDevice}`;
try {
const plaintext = await window.textsecure.storage.protocol.enqueueSenderKeyJob( const plaintext = await window.textsecure.storage.protocol.enqueueSenderKeyJob(
address, address,
() => () =>
@ -1441,39 +1526,26 @@ export default class MessageReceiver extends EventTarget {
), ),
zone zone
); );
return { return { plaintext };
result: {
plaintext,
envelope: newEnvelope,
},
};
} catch (error) {
return {
error,
result: {
envelope: newEnvelope,
},
};
}
} }
window.log.info( window.log.info(
`decrypt/${unidentifiedLogId}: unidentified message/passing to sealedSenderDecryptMessage` `MessageReceiver.decryptSealedSender(${logId}): ` +
'unidentified message/passing to sealedSenderDecryptMessage'
); );
const preKeyStore = new PreKeys(); const preKeyStore = new PreKeys();
const signedPreKeyStore = new SignedPreKeys(); const signedPreKeyStore = new SignedPreKeys();
const sealedSenderIdentifier = newEnvelope.sourceUuid || newEnvelope.source; const sealedSenderIdentifier = envelope.sourceUuid || envelope.source;
const address = `${sealedSenderIdentifier}.${newEnvelope.sourceDevice}`; const address = `${sealedSenderIdentifier}.${envelope.sourceDevice}`;
try {
const unsealedPlaintext = await window.textsecure.storage.protocol.enqueueSessionJob( const unsealedPlaintext = await window.textsecure.storage.protocol.enqueueSessionJob(
address, address,
() => () =>
sealedSenderDecryptMessage( sealedSenderDecryptMessage(
buffer, Buffer.from(ciphertext),
PublicKey.deserialize(Buffer.from(this.serverTrustRoot)), PublicKey.deserialize(Buffer.from(this.serverTrustRoot)),
newEnvelope.serverTimestamp, envelope.serverTimestamp,
localE164 || null, localE164 || null,
localUuid, localUuid,
localDeviceId, localDeviceId,
@ -1485,27 +1557,14 @@ export default class MessageReceiver extends EventTarget {
zone zone
); );
return { return { unsealedPlaintext };
result: { unsealedPlaintext, envelope: newEnvelope },
};
} catch (error) {
return {
error,
result: {
envelope: newEnvelope,
},
};
}
} }
private async innerDecrypt( private async innerDecrypt(
stores: LockedStores, stores: LockedStores,
envelope: ProcessedEnvelope, envelope: ProcessedEnvelope,
ciphertext: Uint8Array ciphertext: Uint8Array
): Promise<{ ): Promise<Uint8Array> {
error?: Error;
result: InnerDecryptResult;
}> {
const { sessionStore, identityKeyStore, zone } = stores; const { sessionStore, identityKeyStore, zone } = stores;
const logId = this.getEnvelopeId(envelope); const logId = this.getEnvelopeId(envelope);
@ -1522,12 +1581,7 @@ export default class MessageReceiver extends EventTarget {
const buffer = Buffer.from(ciphertext); const buffer = Buffer.from(ciphertext);
const plaintextContent = PlaintextContent.deserialize(buffer); const plaintextContent = PlaintextContent.deserialize(buffer);
return { return this.unpad(plaintextContent.body());
result: {
plaintext: this.unpad(plaintextContent.body()),
envelope,
},
};
} }
if (envelope.type === envelopeTypeEnum.CIPHERTEXT) { if (envelope.type === envelopeTypeEnum.CIPHERTEXT) {
window.log.info(`decrypt/${logId}: ciphertext message`); window.log.info(`decrypt/${logId}: ciphertext message`);
@ -1557,12 +1611,7 @@ export default class MessageReceiver extends EventTarget {
), ),
zone zone
); );
return { return plaintext;
result: {
plaintext,
envelope,
},
};
} }
if (envelope.type === envelopeTypeEnum.PREKEY_BUNDLE) { if (envelope.type === envelopeTypeEnum.PREKEY_BUNDLE) {
window.log.info(`decrypt/${logId}: prekey message`); window.log.info(`decrypt/${logId}: prekey message`);
@ -1596,50 +1645,18 @@ export default class MessageReceiver extends EventTarget {
), ),
zone zone
); );
return { return plaintext;
result: {
plaintext,
envelope,
},
};
} }
if (envelope.type === envelopeTypeEnum.UNIDENTIFIED_SENDER) { if (envelope.type === envelopeTypeEnum.UNIDENTIFIED_SENDER) {
window.log.info(`decrypt/${logId}: unidentified message`); window.log.info(`decrypt/${logId}: unidentified message`);
const { result, error } = await this.decryptSealedSender( const { plaintext, unsealedPlaintext } = await this.decryptSealedSender(
stores, stores,
envelope, envelope,
ciphertext ciphertext
); );
if (error) {
return {
error,
result,
};
}
const {
plaintext,
unsealedPlaintext,
isBlocked,
envelope: newEnvelope,
} = result;
if (isBlocked) {
return {
result: {
isBlocked: true,
envelope: newEnvelope,
},
};
}
if (plaintext) { if (plaintext) {
return { return this.unpad(plaintext);
result: {
plaintext: this.unpad(plaintext),
envelope: newEnvelope,
},
};
} }
if (unsealedPlaintext) { if (unsealedPlaintext) {
@ -1653,12 +1670,7 @@ export default class MessageReceiver extends EventTarget {
// 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 { return this.unpad(content);
result: {
plaintext: this.unpad(content),
envelope: newEnvelope,
},
};
} }
throw new Error('Unexpected lack of plaintext from unidentified sender'); throw new Error('Unexpected lack of plaintext from unidentified sender');
@ -1668,35 +1680,17 @@ export default class MessageReceiver extends EventTarget {
private async decrypt( private async decrypt(
stores: LockedStores, stores: LockedStores,
envelope: ProcessedEnvelope, envelope: UnsealedEnvelope,
ciphertext: Uint8Array ciphertext: Uint8Array
): Promise<DecryptResult> { ): Promise<Uint8Array | undefined> {
let newEnvelope: DecryptedEnvelope = envelope;
try { try {
const { result, error } = await this.innerDecrypt( const plaintext = await this.innerDecrypt(stores, envelope, ciphertext);
stores,
envelope,
ciphertext
);
newEnvelope = result.envelope || envelope; return new FIXMEU8(plaintext);
if (error) {
throw error;
}
const { isBlocked, plaintext } = result;
if (isBlocked) {
this.removeFromCache(envelope);
return { plaintext: undefined, envelope: newEnvelope };
}
assert(plaintext, 'Should have plaintext from innerDecrypt');
return { plaintext: new FIXMEU8(plaintext), envelope: newEnvelope };
} catch (error) { } catch (error) {
this.removeFromCache(newEnvelope); this.removeFromCache(envelope);
const uuid = newEnvelope.sourceUuid; const uuid = envelope.sourceUuid;
const deviceId = newEnvelope.sourceDevice; const deviceId = envelope.sourceDevice;
// We don't do a light session reset if it's just a duplicated message // We don't do a light session reset if it's just a duplicated message
if ( if (
@ -1716,19 +1710,19 @@ export default class MessageReceiver extends EventTarget {
} }
if (uuid && deviceId) { if (uuid && deviceId) {
const { usmc } = newEnvelope; const { usmc } = envelope;
const event = new DecryptionErrorEvent({ const event = new DecryptionErrorEvent({
cipherTextBytes: usmc cipherTextBytes: usmc
? typedArrayToArrayBuffer(usmc.contents()) ? typedArrayToArrayBuffer(usmc.contents())
: undefined, : undefined,
cipherTextType: usmc ? usmc.msgType() : undefined, cipherTextType: usmc ? usmc.msgType() : undefined,
contentHint: newEnvelope.contentHint, contentHint: envelope.contentHint,
groupId: newEnvelope.groupId, groupId: envelope.groupId,
receivedAtCounter: newEnvelope.receivedAtCounter, receivedAtCounter: envelope.receivedAtCounter,
receivedAtDate: newEnvelope.receivedAtDate, receivedAtDate: envelope.receivedAtDate,
senderDevice: deviceId, senderDevice: deviceId,
senderUuid: uuid, senderUuid: uuid,
timestamp: newEnvelope.timestamp, timestamp: envelope.timestamp,
}); });
// Avoid deadlocks by scheduling processing on decrypted queue // Avoid deadlocks by scheduling processing on decrypted queue
@ -1737,7 +1731,7 @@ export default class MessageReceiver extends EventTarget {
TaskType.Decrypted TaskType.Decrypted
); );
} else { } else {
const envelopeId = this.getEnvelopeId(newEnvelope); const envelopeId = this.getEnvelopeId(envelope);
window.log.error( window.log.error(
`MessageReceiver.decrypt: Envelope ${envelopeId} missing uuid or deviceId` `MessageReceiver.decrypt: Envelope ${envelopeId} missing uuid or deviceId`
); );
@ -1829,7 +1823,7 @@ export default class MessageReceiver extends EventTarget {
} }
private async handleDataMessage( private async handleDataMessage(
envelope: DecryptedEnvelope, envelope: UnsealedEnvelope,
msg: Proto.IDataMessage msg: Proto.IDataMessage
): Promise<void> { ): Promise<void> {
window.log.info( window.log.info(
@ -1916,23 +1910,6 @@ export default class MessageReceiver extends EventTarget {
return this.dispatchAndWait(ev); return this.dispatchAndWait(ev);
} }
private async decryptLegacyMessage(
stores: LockedStores,
envelope: ProcessedEnvelope
): Promise<DecryptResult> {
window.log.info(
'MessageReceiver.decryptLegacyMessage',
this.getEnvelopeId(envelope)
);
assert(envelope.legacyMessage, 'Should have `legacyMessage` field');
const result = await this.decrypt(stores, envelope, envelope.legacyMessage);
if (!result.plaintext) {
window.log.warn('decryptLegacyMessage: plaintext was falsey');
}
return result;
}
private async innerHandleLegacyMessage( private async innerHandleLegacyMessage(
envelope: ProcessedEnvelope, envelope: ProcessedEnvelope,
plaintext: Uint8Array plaintext: Uint8Array
@ -1941,47 +1918,6 @@ export default class MessageReceiver extends EventTarget {
return this.handleDataMessage(envelope, message); return this.handleDataMessage(envelope, message);
} }
private async decryptContentMessage(
stores: LockedStores,
envelope: ProcessedEnvelope
): Promise<DecryptResult> {
window.log.info(
'MessageReceiver.decryptContentMessage',
this.getEnvelopeId(envelope)
);
assert(envelope.content, 'Should have `content` field');
const result = await this.decrypt(stores, envelope, envelope.content);
if (!result.plaintext) {
window.log.warn('decryptContentMessage: plaintext was falsey');
return result;
}
// 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!
try {
const content = Proto.Content.decode(result.plaintext);
if (
content.senderKeyDistributionMessage &&
Bytes.isNotEmpty(content.senderKeyDistributionMessage)
) {
await this.handleSenderKeyDistributionMessage(
stores,
result.envelope,
content.senderKeyDistributionMessage
);
}
} catch (error) {
const errorString = error && error.stack ? error.stack : error;
window.log.error(
`decryptContentMessage: Failed to process sender key distribution message: ${errorString}`
);
}
return result;
}
private async maybeUpdateTimestamp( private async maybeUpdateTimestamp(
envelope: ProcessedEnvelope envelope: ProcessedEnvelope
): Promise<ProcessedEnvelope> { ): Promise<ProcessedEnvelope> {
@ -2027,9 +1963,9 @@ export default class MessageReceiver extends EventTarget {
}; };
} }
} catch (error) { } catch (error) {
const errorString = error && error.stack ? error.stack : error;
window.log.error( window.log.error(
`maybeUpdateTimestamp/${timestamp}: Failed to process sender key distribution message: ${errorString}` `maybeUpdateTimestamp/${timestamp}: Failed to process sender key ` +
`distribution message: ${Errors.toLogFormat(error)}`
); );
} }
@ -2089,7 +2025,7 @@ export default class MessageReceiver extends EventTarget {
} }
private async handleDecryptionError( private async handleDecryptionError(
envelope: DecryptedEnvelope, envelope: UnsealedEnvelope,
decryptionError: Uint8Array decryptionError: Uint8Array
) { ) {
const logId = this.getEnvelopeId(envelope); const logId = this.getEnvelopeId(envelope);