From 2789340c228f34e6c8151d98da52519744ffd875 Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 12 Sep 2024 16:06:06 +0800 Subject: [PATCH 01/32] wip --- extension/js/common/browser/browser-msg.ts | 2 +- .../thunderbird-element-replacer.ts | 63 ++++++++++++------- extension/js/service_worker/bg-handlers.ts | 6 +- tooling/build-types-and-manifests.ts | 2 +- 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index f67ee9e8175..e4cbbfeb345 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -114,7 +114,7 @@ export namespace Bm { export type ExpirationCacheSet = Promise; export type ExpirationCacheDeleteExpired = Promise; export type ThunderbirdGetCurrentUser = string | undefined; - export type ThunderbirdMsgGet = messenger.messages.MessagePart | undefined; + export type ThunderbirdMsgGet = { attachments: messenger.messages.MessageAttachment[]; messagePart: messenger.messages.MessagePart }; export type ThunderbirdOpenPassphraseDialog = Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type Db = any; // not included in Any below diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 039f78d5c62..89b4ecf4203 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -27,14 +27,14 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public replaceThunderbirdMsgPane = async () => { if (Catch.isThunderbirdMail()) { - const fullMsg = await BrowserMsg.send.bg.await.thunderbirdMsgGet(); - if (!fullMsg) { + const { messagePart, attachments } = await BrowserMsg.send.bg.await.thunderbirdMsgGet(); + if (!messagePart) { return; } else { const acctEmail = await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser(); const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, String(acctEmail)))?.sortedPubkeys ?? []; const signerKeys = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); - if (this.isPublicKeyEncryptedMsg(fullMsg)) { + if (this.isPublicKeyEncryptedMsg(messagePart)) { const result = await MsgUtil.decryptMessage({ kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(String(acctEmail)), encryptedData: this.emailBodyFromThunderbirdMail, @@ -69,7 +69,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { const pgpBlock = this.generatePgpBlockTemplate(decryptionErrorMsg, 'not signed', this.emailBodyFromThunderbirdMail); $('body').html(pgpBlock); // xss-sanitized } - } else if (this.isCleartextMsg(fullMsg)) { + } else if (this.isCleartextMsg(messagePart)) { const message = await openpgp.readCleartextMessage({ cleartextMessage: this.emailBodyFromThunderbirdMail }); const result = await OpenPGPKey.verify(message, await ContactStore.getPubkeyInfos(undefined, signerKeys)); let verificationStatus = ''; @@ -83,7 +83,17 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { const pgpBlock = this.generatePgpBlockTemplate('not encrypted', verificationStatus, signedMessage); $('body').html(pgpBlock); // xss-sanitized } - // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 + } + if (!attachments.length) { + return; + } else { + for (const attachment of attachments) { + const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment.name); + const pgpAttachmentHtml = $('
'); + pgpAttachmentHtml.html(generatedPgpTemplate); // xss-sanitized + $('.pgp_attachments_block').append(pgpAttachmentHtml); // xss-sanitized + // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 + } } } }; @@ -98,17 +108,26 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer {
${Xss.escape(messageToRender)}
+
+
`; }; - private isCleartextMsg = (fullMsg: messenger.messages.MessagePart): boolean => { + private generatePgpAttachmentTemplate = (attachmentName: string): string => { + return `
+
${Xss.escape(attachmentName)}
+
+ `; + }; + + private isCleartextMsg = (messagePart: messenger.messages.MessagePart): boolean => { return ( - (fullMsg.headers && - 'openpgp' in fullMsg.headers && - fullMsg.parts && - fullMsg.parts[0]?.parts?.length === 1 && - fullMsg.parts[0].parts[0].contentType === 'text/plain' && - this.resemblesCleartextMsg(fullMsg.parts[0].parts[0].body?.trim() || '')) || + (messagePart.headers && + 'openpgp' in messagePart.headers && + messagePart.parts && + messagePart.parts[0]?.parts?.length === 1 && + messagePart.parts[0].parts[0].contentType === 'text/plain' && + this.resemblesCleartextMsg(messagePart.parts[0].parts[0].body?.trim() || '')) || false ); }; @@ -122,16 +141,18 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { ); }; - private isPublicKeyEncryptedMsg = (fullMsg: messenger.messages.MessagePart): boolean => { - if (fullMsg.headers && 'openpgp' in fullMsg.headers && fullMsg.parts) { + private isPublicKeyEncryptedMsg = (messagePart: messenger.messages.MessagePart): boolean => { + if (messagePart.headers && 'openpgp' in messagePart.headers && messagePart.parts) { return ( - (fullMsg.parts[0]?.parts?.length === 2 && - fullMsg.parts[0]?.parts[1].contentType === 'application/pgp-encrypted' && - this.resemblesAsciiArmoredMsg(fullMsg.parts[0]?.parts[0].body?.trim() || '')) || - (fullMsg.parts[0]?.parts?.length === 1 && - fullMsg.parts[0]?.contentType === 'multipart/mixed' && - this.resemblesAsciiArmoredMsg(fullMsg.parts[0]?.parts[0].body?.trim() || '')) || - (fullMsg.parts.length === 1 && fullMsg.parts[0]?.contentType === 'text/plain' && this.resemblesAsciiArmoredMsg(fullMsg.parts[0]?.body?.trim() || '')) || + (messagePart.parts[0]?.parts?.length === 2 && + messagePart.parts[0]?.parts[1].contentType === 'application/pgp-encrypted' && + this.resemblesAsciiArmoredMsg(messagePart.parts[0]?.parts[0].body?.trim() || '')) || + (messagePart.parts[0]?.parts?.length === 1 && + messagePart.parts[0]?.contentType === 'multipart/mixed' && + this.resemblesAsciiArmoredMsg(messagePart.parts[0]?.parts[0].body?.trim() || '')) || + (messagePart.parts.length === 1 && + messagePart.parts[0]?.contentType === 'text/plain' && + this.resemblesAsciiArmoredMsg(messagePart.parts[0]?.body?.trim() || '')) || false ); } diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index d33378ea5a6..1064891b489 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -172,10 +172,12 @@ export class BgHandlers { if (tab.id) { const message = await messenger.messageDisplay.getDisplayedMessage(tab.id); if (message?.id) { - return await messenger.messages.getFull(Number(message.id)); + const attachments = await messenger.messages.listAttachments(message.id); + const messagePart = await messenger.messages.getFull(message.id); + return { attachments, messagePart }; } } - return; + return { attachments: [], messagePart: {} as messenger.messages.MessagePart }; }; public static thunderbirdOpenPassphraseDialog = async (r: Bm.ThunderbirdOpenPassphraseDialog): Promise => { diff --git a/tooling/build-types-and-manifests.ts b/tooling/build-types-and-manifests.ts index 0d52a9bd4fb..b3f3d966e4a 100644 --- a/tooling/build-types-and-manifests.ts +++ b/tooling/build-types-and-manifests.ts @@ -62,7 +62,7 @@ addManifest( (manifest.browser_action as messenger._manifest._WebExtensionManifestAction).default_title = 'FlowCrypt'; manifest.name = 'FlowCrypt Encryption for Thunderbird'; manifest.description = 'Simple end-to-end encryption to secure email and attachments on Thunderbird'; - manifest.permissions = [...(manifestV3.permissions ?? []), 'compose', 'messagesRead', 'messagesUpdate', 'messagesModify', 'accountsRead']; + manifest.permissions = [...(manifestV3.permissions ?? []), 'compose', 'downloads', 'messagesRead', 'messagesUpdate', 'messagesModify', 'accountsRead']; manifest.compose_action = { default_title: 'Secure Compose', // eslint-disable-line @typescript-eslint/naming-convention default_icon: '/img/logo/flowcrypt-logo-64-64.png', // eslint-disable-line @typescript-eslint/naming-convention From 0bdd9514dab91d38608d981f033e0b0fa884f0e4 Mon Sep 17 00:00:00 2001 From: martgil Date: Wed, 16 Oct 2024 14:54:44 +0800 Subject: [PATCH 02/32] simplified detection for ASCII armored data --- .../thunderbird-element-replacer.ts | 37 +++++-------------- .../thunderbird-webmail-startup.ts | 2 +- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 89b4ecf4203..1588b949672 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -20,6 +20,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public scrollToReplyBox: (replyMsgId: string) => void; public scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void; private emailBodyFromThunderbirdMail: string; + private thunderbirdEmailSelector = $('div.moz-text-plain'); public getIntervalFunctions = (): IntervalFunction[] => { return [{ interval: 2000, handler: () => this.replaceThunderbirdMsgPane() }]; @@ -34,7 +35,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { const acctEmail = await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser(); const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, String(acctEmail)))?.sortedPubkeys ?? []; const signerKeys = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); - if (this.isPublicKeyEncryptedMsg(messagePart)) { + if (this.isPublicKeyEncryptedMsg()) { const result = await MsgUtil.decryptMessage({ kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(String(acctEmail)), encryptedData: this.emailBodyFromThunderbirdMail, @@ -69,7 +70,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { const pgpBlock = this.generatePgpBlockTemplate(decryptionErrorMsg, 'not signed', this.emailBodyFromThunderbirdMail); $('body').html(pgpBlock); // xss-sanitized } - } else if (this.isCleartextMsg(messagePart)) { + } else if (this.isCleartextMsg()) { const message = await openpgp.readCleartextMessage({ cleartextMessage: this.emailBodyFromThunderbirdMail }); const result = await OpenPGPKey.verify(message, await ContactStore.getPubkeyInfos(undefined, signerKeys)); let verificationStatus = ''; @@ -120,16 +121,9 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { `; }; - private isCleartextMsg = (messagePart: messenger.messages.MessagePart): boolean => { - return ( - (messagePart.headers && - 'openpgp' in messagePart.headers && - messagePart.parts && - messagePart.parts[0]?.parts?.length === 1 && - messagePart.parts[0].parts[0].contentType === 'text/plain' && - this.resemblesCleartextMsg(messagePart.parts[0].parts[0].body?.trim() || '')) || - false - ); + private isCleartextMsg = (): boolean => { + const emailBody = this.thunderbirdEmailSelector.text().trim(); + return this.resemblesCleartextMsg(emailBody); }; private resemblesCleartextMsg = (body: string) => { @@ -141,22 +135,9 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { ); }; - private isPublicKeyEncryptedMsg = (messagePart: messenger.messages.MessagePart): boolean => { - if (messagePart.headers && 'openpgp' in messagePart.headers && messagePart.parts) { - return ( - (messagePart.parts[0]?.parts?.length === 2 && - messagePart.parts[0]?.parts[1].contentType === 'application/pgp-encrypted' && - this.resemblesAsciiArmoredMsg(messagePart.parts[0]?.parts[0].body?.trim() || '')) || - (messagePart.parts[0]?.parts?.length === 1 && - messagePart.parts[0]?.contentType === 'multipart/mixed' && - this.resemblesAsciiArmoredMsg(messagePart.parts[0]?.parts[0].body?.trim() || '')) || - (messagePart.parts.length === 1 && - messagePart.parts[0]?.contentType === 'text/plain' && - this.resemblesAsciiArmoredMsg(messagePart.parts[0]?.body?.trim() || '')) || - false - ); - } - return false; + private isPublicKeyEncryptedMsg = (): boolean => { + const emailBody = this.thunderbirdEmailSelector.text().trim(); + return this.resemblesAsciiArmoredMsg(emailBody); }; private resemblesAsciiArmoredMsg = (body: string): boolean => { diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts index ae9f803a6db..22392ff9139 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts @@ -19,7 +19,7 @@ export class ThunderbirdWebmailStartup { private start = async () => { this.replacer = new ThunderbirdElementReplacer(); - this.replacer.runIntervalFunctionsPeriodically(); + await this.replacer.replaceThunderbirdMsgPane(); // todo: show notification using Thunderbird Notification as contentscript notification or such does not work. // await notifications.showInitial(acctEmail); // notifications.show( From ebbf1bdf2639313453b91805bb9f7af598fa64cb Mon Sep 17 00:00:00 2001 From: martgil Date: Wed, 16 Oct 2024 15:04:29 +0800 Subject: [PATCH 03/32] wip --- .../thunderbird/thunderbird-element-replacer.ts | 1 + test/source/patterns.ts | 2 +- tooling/build-types-and-manifests.ts | 11 ++++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 1588b949672..b1d67266dc5 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -136,6 +136,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; private isPublicKeyEncryptedMsg = (): boolean => { + // todo - recognized email sent via FlowCrypt encrypted contact pages const emailBody = this.thunderbirdEmailSelector.text().trim(); return this.resemblesAsciiArmoredMsg(emailBody); }; diff --git a/test/source/patterns.ts b/test/source/patterns.ts index bfa686ffa51..778c76200e6 100644 --- a/test/source/patterns.ts +++ b/test/source/patterns.ts @@ -112,7 +112,7 @@ for (const buildType of ['chrome-consumer', 'chrome-enterprise', 'thunderbird-co console.error(`${buildType} - The content_security_policy should be a string`); errsFound++; } - const thunderbirdExpectedPermissions = ['compose', 'messagesRead', 'messagesUpdate', 'messagesModify', 'accountsRead']; + const thunderbirdExpectedPermissions = ['compose', 'downloads', 'downloads.open', 'messagesRead', 'messagesUpdate', 'messagesModify', 'accountsRead']; const buildHostPermissions = isManifestV3Build ? manifest.host_permissions : manifest.permissions; for (const expectedHostPermission of thunderbirdExpectedPermissions) { if (!buildHostPermissions?.includes(expectedHostPermission)) { diff --git a/tooling/build-types-and-manifests.ts b/tooling/build-types-and-manifests.ts index e24f7957216..703a6718cff 100644 --- a/tooling/build-types-and-manifests.ts +++ b/tooling/build-types-and-manifests.ts @@ -51,7 +51,16 @@ addManifest( manifest.manifest_version = 2; manifest.name = 'FlowCrypt Encryption for Thunderbird'; manifest.description = 'Simple end-to-end encryption to secure email and attachments on Thunderbird'; - manifest.permissions = [...(manifest.permissions ?? []), 'compose', 'messagesRead', 'messagesUpdate', 'messagesModify', 'accountsRead']; + manifest.permissions = [ + ...(manifest.permissions ?? []), + 'compose', + 'downloads', + 'downloads.open', + 'messagesRead', + 'messagesUpdate', + 'messagesModify', + 'accountsRead', + ]; const manifestV3 = manifest as chrome.runtime.ManifestV3; manifest.web_accessible_resources = manifestV3.web_accessible_resources?.[0].resources; manifest.content_security_policy = manifestV3.content_security_policy?.extension_pages; From aa4feac94b6d6512d977d484912d21694bb46f7c Mon Sep 17 00:00:00 2001 From: martgil Date: Wed, 16 Oct 2024 16:36:09 +0800 Subject: [PATCH 04/32] bind event handler to attachment download button --- .../thunderbird-element-replacer.ts | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index b1d67266dc5..efee4470e2a 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -89,10 +89,8 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { return; } else { for (const attachment of attachments) { - const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment.name); - const pgpAttachmentHtml = $('
'); - pgpAttachmentHtml.html(generatedPgpTemplate); // xss-sanitized - $('.pgp_attachments_block').append(pgpAttachmentHtml); // xss-sanitized + const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment); + $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 } } @@ -114,11 +112,24 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer {
`; }; - private generatePgpAttachmentTemplate = (attachmentName: string): string => { - return `
-
${Xss.escape(attachmentName)}
-
- `; + private generatePgpAttachmentTemplate = (attachment: messenger.messages.MessageAttachment) => { + const attachmentHtmlRoot = $('
'); + const attachmentFilename = $('
'); + attachmentFilename.addClass('attachment_name').text(Xss.escape(attachment.name)); + const attachmentDownloadBtn = $('
'); + attachmentDownloadBtn + .addClass('attachment_download') + .text('download') + .on('click', () => { + this.downloadThunderbirdAttachmentHandler(attachment); + }); + attachmentHtmlRoot.append(attachmentFilename); // xss-escaped + attachmentHtmlRoot.append(attachmentDownloadBtn); // xss-escaped + return attachmentHtmlRoot; + }; + + private downloadThunderbirdAttachmentHandler = (attachment: messenger.messages.MessageAttachment) => { + console.log('debug:', attachment); }; private isCleartextMsg = (): boolean => { From 2fdeba4d01119f9d4a38a5127a9091f347fbec62 Mon Sep 17 00:00:00 2001 From: martgil Date: Wed, 16 Oct 2024 18:03:14 +0800 Subject: [PATCH 05/32] add new background message for obtaining current tab --- extension/js/common/browser/browser-msg.ts | 4 ++++ .../webmail/thunderbird/thunderbird-element-replacer.ts | 8 +++++--- extension/js/service_worker/background.ts | 1 + extension/js/service_worker/bg-handlers.ts | 5 +++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index 0065365382e..d9e3a52e692 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -114,6 +114,7 @@ export namespace Bm { export type ExpirationCacheGet = Promise; export type ExpirationCacheSet = Promise; export type ExpirationCacheDeleteExpired = Promise; + export type ThunderbirdGetActiveTabInfo = number | undefined; export type ThunderbirdGetCurrentUser = string | undefined; export type ThunderbirdMsgGet = { attachments: messenger.messages.MessageAttachment[]; messagePart: messenger.messages.MessagePart }; export type ThunderbirdOpenPassphraseDialog = Promise; @@ -134,6 +135,7 @@ export namespace Bm { | ExpirationCacheDeleteExpired | AjaxGmailAttachmentGetChunk | ConfirmationResult + | ThunderbirdGetActiveTabInfo | ThunderbirdMsgGet; } @@ -239,6 +241,8 @@ export class BrowserMsg { BrowserMsg.sendAwait(undefined, 'expirationCacheSet', bm, true) as Promise, expirationCacheDeleteExpired: (bm: Bm.ExpirationCacheDeleteExpired) => BrowserMsg.sendAwait(undefined, 'expirationCacheDeleteExpired', bm, true) as Promise, + thunderbirdGetActiveTabInfo: () => + BrowserMsg.sendAwait(undefined, 'thunderbirdGetActiveTabInfo', undefined, true) as Promise, thunderbirdGetCurrentUser: () => BrowserMsg.sendAwait(undefined, 'thunderbirdGetCurrentUser', undefined, true) as Promise, thunderbirdMsgGet: () => BrowserMsg.sendAwait(undefined, 'thunderbirdMsgGet', undefined, true) as Promise, diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index efee4470e2a..715f04f58ce 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -120,16 +120,18 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { attachmentDownloadBtn .addClass('attachment_download') .text('download') - .on('click', () => { - this.downloadThunderbirdAttachmentHandler(attachment); + .on('click', async () => { + await this.downloadThunderbirdAttachmentHandler(attachment); }); attachmentHtmlRoot.append(attachmentFilename); // xss-escaped attachmentHtmlRoot.append(attachmentDownloadBtn); // xss-escaped return attachmentHtmlRoot; }; - private downloadThunderbirdAttachmentHandler = (attachment: messenger.messages.MessageAttachment) => { + private downloadThunderbirdAttachmentHandler = async (attachment: messenger.messages.MessageAttachment) => { console.log('debug:', attachment); + const messageId = await BrowserMsg.send.bg.await.thunderbirdGetActiveTabInfo(); + console.log(messageId); }; private isCleartextMsg = (): boolean => { diff --git a/extension/js/service_worker/background.ts b/extension/js/service_worker/background.ts index bda3603c0a7..72a5c9fb1f0 100644 --- a/extension/js/service_worker/background.ts +++ b/extension/js/service_worker/background.ts @@ -73,6 +73,7 @@ console.info('background.js service worker starting'); if (Catch.isThunderbirdMail()) { BgHandlers.thunderbirdSecureComposeHandler(); await BgHandlers.thunderbirdContentScriptRegistration(); + BrowserMsg.bgAddListener('thunderbirdGetActiveTabInfo', BgHandlers.thunderbirdGetActiveTabInfo); BrowserMsg.bgAddListener('thunderbirdGetCurrentUser', BgHandlers.thunderbirdGetCurrentUserHandler); BrowserMsg.bgAddListener('thunderbirdMsgGet', BgHandlers.thunderbirdMsgGetHandler); BrowserMsg.bgAddListener('thunderbirdOpenPassphraseDialog', BgHandlers.thunderbirdOpenPassphraseDialog); diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index c32a4e284a9..22e2e8dbe9c 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -162,6 +162,11 @@ export class BgHandlers { }); }; + public static thunderbirdGetActiveTabInfo = async (): Promise => { + const tabs = await messenger.tabs.query({ active: true, currentWindow: true }); + return tabs[0].id; + }; + public static thunderbirdGetCurrentUserHandler = async (): Promise => { const [tab] = await messenger.tabs.query({ active: true, currentWindow: true }); if (tab.id) { From 07a96bf9c3754db896d670f30954b31bc07044fb Mon Sep 17 00:00:00 2001 From: martgil Date: Wed, 16 Oct 2024 18:52:40 +0800 Subject: [PATCH 06/32] add attachment download event handler (baseline) --- extension/js/common/browser/browser-msg.ts | 10 ++++++---- .../thunderbird/thunderbird-element-replacer.ts | 4 +--- extension/js/service_worker/background.ts | 2 +- extension/js/service_worker/bg-handlers.ts | 15 ++++++++++++--- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index d9e3a52e692..e4df9fea846 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -98,6 +98,7 @@ export namespace Bm { export type PgpBlockRetry = { frameId: string; messageSender: Dest }; export type PgpBlockReady = { frameId: string; messageSender: Dest }; export type ThunderbirdOpenPassphraseDialog = { acctEmail: string; longids: string }; + export type ThunderbirdAttachmentDownload = { attachment: messenger.messages.MessageAttachment }; export namespace Res { export type GetActiveTabInfo = { @@ -114,7 +115,7 @@ export namespace Bm { export type ExpirationCacheGet = Promise; export type ExpirationCacheSet = Promise; export type ExpirationCacheDeleteExpired = Promise; - export type ThunderbirdGetActiveTabInfo = number | undefined; + export type ThunderbirdAttachmentDownload = Promise; export type ThunderbirdGetCurrentUser = string | undefined; export type ThunderbirdMsgGet = { attachments: messenger.messages.MessageAttachment[]; messagePart: messenger.messages.MessagePart }; export type ThunderbirdOpenPassphraseDialog = Promise; @@ -135,7 +136,7 @@ export namespace Bm { | ExpirationCacheDeleteExpired | AjaxGmailAttachmentGetChunk | ConfirmationResult - | ThunderbirdGetActiveTabInfo + | ThunderbirdAttachmentDownload | ThunderbirdMsgGet; } @@ -177,6 +178,7 @@ export namespace Bm { | PgpBlockReady | PgpBlockRetry | ConfirmationResult + | ThunderbirdAttachmentDownload | ThunderbirdOpenPassphraseDialog | Ajax; @@ -241,8 +243,8 @@ export class BrowserMsg { BrowserMsg.sendAwait(undefined, 'expirationCacheSet', bm, true) as Promise, expirationCacheDeleteExpired: (bm: Bm.ExpirationCacheDeleteExpired) => BrowserMsg.sendAwait(undefined, 'expirationCacheDeleteExpired', bm, true) as Promise, - thunderbirdGetActiveTabInfo: () => - BrowserMsg.sendAwait(undefined, 'thunderbirdGetActiveTabInfo', undefined, true) as Promise, + thunderbirdAttachmentDownload: (bm: Bm.ThunderbirdAttachmentDownload) => + BrowserMsg.sendAwait(undefined, 'thunderbirdAttachmentDownload', bm, true) as Promise, thunderbirdGetCurrentUser: () => BrowserMsg.sendAwait(undefined, 'thunderbirdGetCurrentUser', undefined, true) as Promise, thunderbirdMsgGet: () => BrowserMsg.sendAwait(undefined, 'thunderbirdMsgGet', undefined, true) as Promise, diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 715f04f58ce..2ff684dd25d 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -129,9 +129,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; private downloadThunderbirdAttachmentHandler = async (attachment: messenger.messages.MessageAttachment) => { - console.log('debug:', attachment); - const messageId = await BrowserMsg.send.bg.await.thunderbirdGetActiveTabInfo(); - console.log(messageId); + await BrowserMsg.send.bg.await.thunderbirdAttachmentDownload({ attachment }); }; private isCleartextMsg = (): boolean => { diff --git a/extension/js/service_worker/background.ts b/extension/js/service_worker/background.ts index 72a5c9fb1f0..045e50c4f43 100644 --- a/extension/js/service_worker/background.ts +++ b/extension/js/service_worker/background.ts @@ -73,9 +73,9 @@ console.info('background.js service worker starting'); if (Catch.isThunderbirdMail()) { BgHandlers.thunderbirdSecureComposeHandler(); await BgHandlers.thunderbirdContentScriptRegistration(); - BrowserMsg.bgAddListener('thunderbirdGetActiveTabInfo', BgHandlers.thunderbirdGetActiveTabInfo); BrowserMsg.bgAddListener('thunderbirdGetCurrentUser', BgHandlers.thunderbirdGetCurrentUserHandler); BrowserMsg.bgAddListener('thunderbirdMsgGet', BgHandlers.thunderbirdMsgGetHandler); + BrowserMsg.bgAddListener('thunderbirdAttachmentDownload', BgHandlers.thunderbirdAttachmentDownload); BrowserMsg.bgAddListener('thunderbirdOpenPassphraseDialog', BgHandlers.thunderbirdOpenPassphraseDialog); } })().catch(Catch.reportErr); diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index 22e2e8dbe9c..5185366d1b1 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -162,9 +162,18 @@ export class BgHandlers { }); }; - public static thunderbirdGetActiveTabInfo = async (): Promise => { - const tabs = await messenger.tabs.query({ active: true, currentWindow: true }); - return tabs[0].id; + public static thunderbirdAttachmentDownload = async (r: Bm.ThunderbirdAttachmentDownload): Promise => { + const [tab] = await messenger.mailTabs.query({ active: true, currentWindow: true }); + if (tab.id) { + const downloadableAttachment = await messenger.messages.getAttachmentFile(tab.id, r.attachment.partName); + const fileUrl = URL.createObjectURL(downloadableAttachment); + await browser.downloads.download({ + url: fileUrl, + filename: r.attachment.name, + saveAs: true, + }); + URL.revokeObjectURL(fileUrl); + } }; public static thunderbirdGetCurrentUserHandler = async (): Promise => { From 6127160d37e66d7f88322ca5c2d25a48578408a7 Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 17 Oct 2024 14:44:53 +0800 Subject: [PATCH 07/32] add attachment decryption --- extension/js/common/browser/browser-msg.ts | 13 +++--- .../thunderbird-element-replacer.ts | 46 +++++++++++++------ extension/js/service_worker/background.ts | 2 +- extension/js/service_worker/bg-handlers.ts | 17 ++++--- 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index e4df9fea846..c02d6aed629 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -20,6 +20,7 @@ import { Ui } from './ui.js'; import { AuthRes } from '../api/authentication/generic/oauth.js'; import { GlobalStore } from '../platform/store/global-store.js'; import { BgUtils } from '../../service_worker/bgutils.js'; +import { Attachment } from '../core/attachment.js'; export type GoogleAuthWindowResult$result = 'Success' | 'Denied' | 'Error' | 'Closed'; export type ScreenDimensions = { width: number; height: number; availLeft: number; availTop: number }; @@ -98,7 +99,7 @@ export namespace Bm { export type PgpBlockRetry = { frameId: string; messageSender: Dest }; export type PgpBlockReady = { frameId: string; messageSender: Dest }; export type ThunderbirdOpenPassphraseDialog = { acctEmail: string; longids: string }; - export type ThunderbirdAttachmentDownload = { attachment: messenger.messages.MessageAttachment }; + export type ThunderbirdGetDownloadableAttachment = { attachment: messenger.messages.MessageAttachment }; export namespace Res { export type GetActiveTabInfo = { @@ -115,7 +116,7 @@ export namespace Bm { export type ExpirationCacheGet = Promise; export type ExpirationCacheSet = Promise; export type ExpirationCacheDeleteExpired = Promise; - export type ThunderbirdAttachmentDownload = Promise; + export type ThunderbirdGetDownloadableAttachment = Attachment | undefined; export type ThunderbirdGetCurrentUser = string | undefined; export type ThunderbirdMsgGet = { attachments: messenger.messages.MessageAttachment[]; messagePart: messenger.messages.MessagePart }; export type ThunderbirdOpenPassphraseDialog = Promise; @@ -136,7 +137,7 @@ export namespace Bm { | ExpirationCacheDeleteExpired | AjaxGmailAttachmentGetChunk | ConfirmationResult - | ThunderbirdAttachmentDownload + | ThunderbirdGetDownloadableAttachment | ThunderbirdMsgGet; } @@ -178,7 +179,7 @@ export namespace Bm { | PgpBlockReady | PgpBlockRetry | ConfirmationResult - | ThunderbirdAttachmentDownload + | ThunderbirdGetDownloadableAttachment | ThunderbirdOpenPassphraseDialog | Ajax; @@ -243,8 +244,8 @@ export class BrowserMsg { BrowserMsg.sendAwait(undefined, 'expirationCacheSet', bm, true) as Promise, expirationCacheDeleteExpired: (bm: Bm.ExpirationCacheDeleteExpired) => BrowserMsg.sendAwait(undefined, 'expirationCacheDeleteExpired', bm, true) as Promise, - thunderbirdAttachmentDownload: (bm: Bm.ThunderbirdAttachmentDownload) => - BrowserMsg.sendAwait(undefined, 'thunderbirdAttachmentDownload', bm, true) as Promise, + thunderbirdGetDownloadableAttachment: (bm: Bm.ThunderbirdGetDownloadableAttachment) => + BrowserMsg.sendAwait(undefined, 'thunderbirdGetDownloadableAttachment', bm, true) as Promise, thunderbirdGetCurrentUser: () => BrowserMsg.sendAwait(undefined, 'thunderbirdGetCurrentUser', undefined, true) as Promise, thunderbirdMsgGet: () => BrowserMsg.sendAwait(undefined, 'thunderbirdMsgGet', undefined, true) as Promise, diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 2ff684dd25d..e1bd0ce3c68 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -19,6 +19,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public reinsertReplyBox: (replyMsgId: string) => void; public scrollToReplyBox: (replyMsgId: string) => void; public scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void; + private acctEmail: string; private emailBodyFromThunderbirdMail: string; private thunderbirdEmailSelector = $('div.moz-text-plain'); @@ -32,12 +33,13 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { if (!messagePart) { return; } else { - const acctEmail = await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser(); - const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, String(acctEmail)))?.sortedPubkeys ?? []; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.acctEmail = (await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser())!; + const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, this.acctEmail))?.sortedPubkeys ?? []; const signerKeys = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); if (this.isPublicKeyEncryptedMsg()) { const result = await MsgUtil.decryptMessage({ - kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(String(acctEmail)), + kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), encryptedData: this.emailBodyFromThunderbirdMail, verificationPubs: signerKeys, }); @@ -70,6 +72,15 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { const pgpBlock = this.generatePgpBlockTemplate(decryptionErrorMsg, 'not signed', this.emailBodyFromThunderbirdMail); $('body').html(pgpBlock); // xss-sanitized } + if (!attachments.length) { + return; + } else { + for (const attachment of attachments) { + const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment); + $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized + // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 + } + } } else if (this.isCleartextMsg()) { const message = await openpgp.readCleartextMessage({ cleartextMessage: this.emailBodyFromThunderbirdMail }); const result = await OpenPGPKey.verify(message, await ContactStore.getPubkeyInfos(undefined, signerKeys)); @@ -85,15 +96,6 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { $('body').html(pgpBlock); // xss-sanitized } } - if (!attachments.length) { - return; - } else { - for (const attachment of attachments) { - const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment); - $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized - // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 - } - } } }; @@ -129,7 +131,25 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; private downloadThunderbirdAttachmentHandler = async (attachment: messenger.messages.MessageAttachment) => { - await BrowserMsg.send.bg.await.thunderbirdAttachmentDownload({ attachment }); + const flowCryptAttachment = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); + if (flowCryptAttachment) { + const result = await MsgUtil.decryptMessage({ + kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), + encryptedData: this.emailBodyFromThunderbirdMail, + verificationPubs: [], // todo: #4158 signature verification of attachments + }); + if (result.success && result.content) { + console.log('debug: download me'); + // todo - create separate background message for download prompt and file download + // const fileUrl = URL.createObjectURL(rawAttachment); + // await browser.downloads.download({ + // url: fileUrl, + // filename: r.attachment.name, + // saveAs: true, + // }); + // URL.revokeObjectURL(fileUrl); + } + } }; private isCleartextMsg = (): boolean => { diff --git a/extension/js/service_worker/background.ts b/extension/js/service_worker/background.ts index 045e50c4f43..78507b69671 100644 --- a/extension/js/service_worker/background.ts +++ b/extension/js/service_worker/background.ts @@ -75,7 +75,7 @@ console.info('background.js service worker starting'); await BgHandlers.thunderbirdContentScriptRegistration(); BrowserMsg.bgAddListener('thunderbirdGetCurrentUser', BgHandlers.thunderbirdGetCurrentUserHandler); BrowserMsg.bgAddListener('thunderbirdMsgGet', BgHandlers.thunderbirdMsgGetHandler); - BrowserMsg.bgAddListener('thunderbirdAttachmentDownload', BgHandlers.thunderbirdAttachmentDownload); + BrowserMsg.bgAddListener('thunderbirdGetDownloadableAttachment', BgHandlers.thunderbirdGetDownloadableAttachment); BrowserMsg.bgAddListener('thunderbirdOpenPassphraseDialog', BgHandlers.thunderbirdOpenPassphraseDialog); } })().catch(Catch.reportErr); diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index 5185366d1b1..a659642dbaf 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -13,6 +13,7 @@ import { GoogleOAuth } from '../common/api/authentication/google/google-oauth.js import { AcctStore } from '../common/platform/store/acct-store.js'; import { ConfiguredIdpOAuth } from '../common/api/authentication/configured-idp-oauth.js'; import { Url, Str } from '../common/core/common.js'; +import { Attachment } from '../common/core/attachment.js'; export class BgHandlers { public static openSettingsPageHandler: Bm.AsyncResponselessHandler = async ({ page, path, pageUrlParams, addNewAcct, acctEmail }: Bm.Settings) => { @@ -162,18 +163,16 @@ export class BgHandlers { }); }; - public static thunderbirdAttachmentDownload = async (r: Bm.ThunderbirdAttachmentDownload): Promise => { + public static thunderbirdGetDownloadableAttachment = async ( + r: Bm.ThunderbirdGetDownloadableAttachment + ): Promise => { const [tab] = await messenger.mailTabs.query({ active: true, currentWindow: true }); if (tab.id) { - const downloadableAttachment = await messenger.messages.getAttachmentFile(tab.id, r.attachment.partName); - const fileUrl = URL.createObjectURL(downloadableAttachment); - await browser.downloads.download({ - url: fileUrl, - filename: r.attachment.name, - saveAs: true, - }); - URL.revokeObjectURL(fileUrl); + const rawAttachment = await messenger.messages.getAttachmentFile(tab.id, r.attachment.partName); + const data = new Uint8Array(await rawAttachment.arrayBuffer()); + return new Attachment({ data }); } + return; }; public static thunderbirdGetCurrentUserHandler = async (): Promise => { From 66d0baaf6d7d577aa467b9e42ce144f4a5da1e3e Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 17 Oct 2024 15:55:16 +0800 Subject: [PATCH 08/32] add decrypted attachment download --- extension/js/common/browser/browser-msg.ts | 8 ++++-- .../thunderbird-element-replacer.ts | 25 ++++++++----------- extension/js/service_worker/background.ts | 1 + extension/js/service_worker/bg-handlers.ts | 16 +++++++++++- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index c02d6aed629..4c566caa5c1 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -20,7 +20,6 @@ import { Ui } from './ui.js'; import { AuthRes } from '../api/authentication/generic/oauth.js'; import { GlobalStore } from '../platform/store/global-store.js'; import { BgUtils } from '../../service_worker/bgutils.js'; -import { Attachment } from '../core/attachment.js'; export type GoogleAuthWindowResult$result = 'Success' | 'Denied' | 'Error' | 'Closed'; export type ScreenDimensions = { width: number; height: number; availLeft: number; availTop: number }; @@ -100,6 +99,7 @@ export namespace Bm { export type PgpBlockReady = { frameId: string; messageSender: Dest }; export type ThunderbirdOpenPassphraseDialog = { acctEmail: string; longids: string }; export type ThunderbirdGetDownloadableAttachment = { attachment: messenger.messages.MessageAttachment }; + export type ThunderbirdInitiateAttachmentDownload = { decryptedFileName: string; decryptedContent: Buf }; export namespace Res { export type GetActiveTabInfo = { @@ -116,10 +116,11 @@ export namespace Bm { export type ExpirationCacheGet = Promise; export type ExpirationCacheSet = Promise; export type ExpirationCacheDeleteExpired = Promise; - export type ThunderbirdGetDownloadableAttachment = Attachment | undefined; + export type ThunderbirdGetDownloadableAttachment = Buf | undefined; export type ThunderbirdGetCurrentUser = string | undefined; export type ThunderbirdMsgGet = { attachments: messenger.messages.MessageAttachment[]; messagePart: messenger.messages.MessagePart }; export type ThunderbirdOpenPassphraseDialog = Promise; + export type ThunderbirdInitiateAttachmentDownload = Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type Db = any; // not included in Any below // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -181,6 +182,7 @@ export namespace Bm { | ConfirmationResult | ThunderbirdGetDownloadableAttachment | ThunderbirdOpenPassphraseDialog + | ThunderbirdInitiateAttachmentDownload | Ajax; export type AsyncRespondingHandler = (req: AnyRequest) => Promise; @@ -246,6 +248,8 @@ export class BrowserMsg { BrowserMsg.sendAwait(undefined, 'expirationCacheDeleteExpired', bm, true) as Promise, thunderbirdGetDownloadableAttachment: (bm: Bm.ThunderbirdGetDownloadableAttachment) => BrowserMsg.sendAwait(undefined, 'thunderbirdGetDownloadableAttachment', bm, true) as Promise, + thunderbirdInitiateAttachmentDownload: (bm: Bm.ThunderbirdInitiateAttachmentDownload) => + BrowserMsg.sendAwait(undefined, 'thunderbirdInitiateAttachmentDownload', bm, true) as Promise, thunderbirdGetCurrentUser: () => BrowserMsg.sendAwait(undefined, 'thunderbirdGetCurrentUser', undefined, true) as Promise, thunderbirdMsgGet: () => BrowserMsg.sendAwait(undefined, 'thunderbirdMsgGet', undefined, true) as Promise, diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index e1bd0ce3c68..8058a8e5319 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -76,8 +76,11 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { return; } else { for (const attachment of attachments) { - const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment); - $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized + if (attachment.name.endsWith('.pgp')) { + const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment); + $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized + } + // todo: detect encrypted message send as attachment and render it when email body is empty // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 } } @@ -131,23 +134,17 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; private downloadThunderbirdAttachmentHandler = async (attachment: messenger.messages.MessageAttachment) => { - const flowCryptAttachment = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); - if (flowCryptAttachment) { + const encryptedData = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); + if (encryptedData) { const result = await MsgUtil.decryptMessage({ kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), - encryptedData: this.emailBodyFromThunderbirdMail, + encryptedData, verificationPubs: [], // todo: #4158 signature verification of attachments }); if (result.success && result.content) { - console.log('debug: download me'); - // todo - create separate background message for download prompt and file download - // const fileUrl = URL.createObjectURL(rawAttachment); - // await browser.downloads.download({ - // url: fileUrl, - // filename: r.attachment.name, - // saveAs: true, - // }); - // URL.revokeObjectURL(fileUrl); + const decryptedFileName = attachment.name.replace(/\.(pgp|gpg|asc)$/i, ''); + const decryptedContent = result.content; + await BrowserMsg.send.bg.await.thunderbirdInitiateAttachmentDownload({ decryptedFileName, decryptedContent }); } } }; diff --git a/extension/js/service_worker/background.ts b/extension/js/service_worker/background.ts index 78507b69671..34a09c3499b 100644 --- a/extension/js/service_worker/background.ts +++ b/extension/js/service_worker/background.ts @@ -76,6 +76,7 @@ console.info('background.js service worker starting'); BrowserMsg.bgAddListener('thunderbirdGetCurrentUser', BgHandlers.thunderbirdGetCurrentUserHandler); BrowserMsg.bgAddListener('thunderbirdMsgGet', BgHandlers.thunderbirdMsgGetHandler); BrowserMsg.bgAddListener('thunderbirdGetDownloadableAttachment', BgHandlers.thunderbirdGetDownloadableAttachment); + BrowserMsg.bgAddListener('thunderbirdInitiateAttachmentDownload', BgHandlers.thunderbirdInitiateAttachmentDownload); BrowserMsg.bgAddListener('thunderbirdOpenPassphraseDialog', BgHandlers.thunderbirdOpenPassphraseDialog); } })().catch(Catch.reportErr); diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index a659642dbaf..d45a3570a9e 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -170,11 +170,25 @@ export class BgHandlers { if (tab.id) { const rawAttachment = await messenger.messages.getAttachmentFile(tab.id, r.attachment.partName); const data = new Uint8Array(await rawAttachment.arrayBuffer()); - return new Attachment({ data }); + return new Attachment({ data }).getData(); } return; }; + public static thunderbirdInitiateAttachmentDownload = async ( + r: Bm.ThunderbirdInitiateAttachmentDownload + ): Promise => { + // todo - add prompt using messenger.notifications.create. requires `notifications` permission; + const blob = new Blob([r.decryptedContent]); + const fileUrl = URL.createObjectURL(blob); + await browser.downloads.download({ + url: fileUrl, + filename: r.decryptedFileName, + saveAs: true, + }); + URL.revokeObjectURL(fileUrl); + }; + public static thunderbirdGetCurrentUserHandler = async (): Promise => { const [tab] = await messenger.tabs.query({ active: true, currentWindow: true }); if (tab.id) { From 5e9a94669450abbf8634d2b81a4117c9a2909bf5 Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 17 Oct 2024 16:59:27 +0800 Subject: [PATCH 09/32] wip --- .../thunderbird-element-replacer.ts | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 8058a8e5319..df3410e5e51 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -30,9 +30,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public replaceThunderbirdMsgPane = async () => { if (Catch.isThunderbirdMail()) { const { messagePart, attachments } = await BrowserMsg.send.bg.await.thunderbirdMsgGet(); - if (!messagePart) { - return; - } else { + if (messagePart) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.acctEmail = (await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser())!; const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, this.acctEmail))?.sortedPubkeys ?? []; @@ -72,18 +70,6 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { const pgpBlock = this.generatePgpBlockTemplate(decryptionErrorMsg, 'not signed', this.emailBodyFromThunderbirdMail); $('body').html(pgpBlock); // xss-sanitized } - if (!attachments.length) { - return; - } else { - for (const attachment of attachments) { - if (attachment.name.endsWith('.pgp')) { - const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment); - $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized - } - // todo: detect encrypted message send as attachment and render it when email body is empty - // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 - } - } } else if (this.isCleartextMsg()) { const message = await openpgp.readCleartextMessage({ cleartextMessage: this.emailBodyFromThunderbirdMail }); const result = await OpenPGPKey.verify(message, await ContactStore.getPubkeyInfos(undefined, signerKeys)); @@ -99,6 +85,16 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { $('body').html(pgpBlock); // xss-sanitized } } + if (attachments.length) { + for (const attachment of attachments) { + if (attachment.name.endsWith('.pgp')) { + const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment); + $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized + } + // todo: detect encrypted message send as attachment and render it when email body is empty + // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 + } + } } }; From bb50a728a62900ea5342eab6f136e702a7e380df Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 17 Oct 2024 17:35:29 +0800 Subject: [PATCH 10/32] wip --- .../webmail/thunderbird/thunderbird-webmail-startup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts index 22392ff9139..ae9f803a6db 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts @@ -19,7 +19,7 @@ export class ThunderbirdWebmailStartup { private start = async () => { this.replacer = new ThunderbirdElementReplacer(); - await this.replacer.replaceThunderbirdMsgPane(); + this.replacer.runIntervalFunctionsPeriodically(); // todo: show notification using Thunderbird Notification as contentscript notification or such does not work. // await notifications.showInitial(acctEmail); // notifications.show( From 4a95af1494cd544df7a0cdaa0ecde00eb8644d6e Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 17 Oct 2024 19:07:06 +0800 Subject: [PATCH 11/32] add ui adjustment --- extension/css/cryptup.css | 35 +++++++++++++++++++ .../thunderbird-element-replacer.ts | 15 ++++---- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/extension/css/cryptup.css b/extension/css/cryptup.css index cea22762c56..6de5852c5bd 100644 --- a/extension/css/cryptup.css +++ b/extension/css/cryptup.css @@ -3502,6 +3502,41 @@ body#settings div.webmail_notifications div.webmail_notification a:hover { text-decoration: none; } +/* thunderbird ui css */ + +.thunderbird_attachment_root { + display: inline-flex; + margin: 0 7px 7px 0; + background: #fff; + align-items: center; + align-content: center; + border: 1px solid gray; + padding: 2px; + cursor: pointer; +} + +.thunderbird_attachment_root div { + display: inline-block; +} + +.thunderbird_attachment_root .thunderbird_attachment_name { + margin: 0 2px; +} + +.thunderbird_attachment_root .thunderbird_attachment_icon { + height: 40px; + width: 40px; + background: #fff; + overflow: hidden; +} + +.thunderbird_attachment_root .thunderbird_attachment_download { + background: url('/img/svgs/download-link.svg') center / auto no-repeat; + padding: 2px; + height: 40px; + width: 40px; +} + /* print class */ @media screen { .printable { diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index df3410e5e51..899d426b059 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -114,16 +114,17 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; private generatePgpAttachmentTemplate = (attachment: messenger.messages.MessageAttachment) => { - const attachmentHtmlRoot = $('
'); - const attachmentFilename = $('
'); - attachmentFilename.addClass('attachment_name').text(Xss.escape(attachment.name)); - const attachmentDownloadBtn = $('
'); - attachmentDownloadBtn - .addClass('attachment_download') - .text('download') + const attachmentHtmlRoot = $('
').addClass('thunderbird_attachment_root'); + const attachmentFileTypeIcon = $('').addClass('thunderbird_attachment_icon'); + attachmentFileTypeIcon.attr('alt', Xss.escape(attachment.name)); + attachmentFileTypeIcon.attr('src', '/image/fileformat/generic.png'); + const attachmentFilename = $('
').addClass('thunderbird_attachment_name').text(Xss.escape(attachment.name)); + const attachmentDownloadBtn = $('
') + .addClass('thunderbird_attachment_download') .on('click', async () => { await this.downloadThunderbirdAttachmentHandler(attachment); }); + attachmentHtmlRoot.append(attachmentFileTypeIcon); // xss-escaped attachmentHtmlRoot.append(attachmentFilename); // xss-escaped attachmentHtmlRoot.append(attachmentDownloadBtn); // xss-escaped return attachmentHtmlRoot; From d68265bc0a9c11cea140b387e9807e1c01b5d936 Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 18 Oct 2024 14:09:33 +0800 Subject: [PATCH 12/32] added ui improvements --- extension/css/cryptup.css | 37 +++++++++++++++---- .../thunderbird-element-replacer.ts | 16 +++++--- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/extension/css/cryptup.css b/extension/css/cryptup.css index 6de5852c5bd..0abba6110c8 100644 --- a/extension/css/cryptup.css +++ b/extension/css/cryptup.css @@ -3505,14 +3505,22 @@ body#settings div.webmail_notifications div.webmail_notification a:hover { /* thunderbird ui css */ .thunderbird_attachment_root { - display: inline-flex; margin: 0 7px 7px 0; + padding: 6px 4px; background: #fff; + border: 1px solid #d8d8d8; + color: #444; + text-overflow: ellipsis; align-items: center; align-content: center; - border: 1px solid gray; - padding: 2px; + width: 240px; + max-width: 270px; + display: inline-flex; cursor: pointer; + position: relative; + border-radius: 2px; + height: 40px; + font-size: 16px; } .thunderbird_attachment_root div { @@ -3528,13 +3536,28 @@ body#settings div.webmail_notifications div.webmail_notification a:hover { width: 40px; background: #fff; overflow: hidden; + opacity: 0.4; } .thunderbird_attachment_root .thunderbird_attachment_download { - background: url('/img/svgs/download-link.svg') center / auto no-repeat; - padding: 2px; - height: 40px; - width: 40px; + display: none; + padding: 6px; + height: 30px; + width: 30px; + right: 0; + position: absolute; + background: #333; + opacity: 0.88; + border-radius: 2px; + margin: 3px 6px; +} + +.thunderbird_attachment_root:hover .thunderbird_attachment_download { + display: inline-block; +} + +.thunderbird_attachment_download img { + width: 100%; } /* print class */ diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 899d426b059..18f27112a88 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -114,19 +114,25 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; private generatePgpAttachmentTemplate = (attachment: messenger.messages.MessageAttachment) => { + const uiFileExtensions = ['excel', 'word', 'png', 'jpg', 'generic']; const attachmentHtmlRoot = $('
').addClass('thunderbird_attachment_root'); const attachmentFileTypeIcon = $('').addClass('thunderbird_attachment_icon'); attachmentFileTypeIcon.attr('alt', Xss.escape(attachment.name)); - attachmentFileTypeIcon.attr('src', '/image/fileformat/generic.png'); - const attachmentFilename = $('
').addClass('thunderbird_attachment_name').text(Xss.escape(attachment.name)); + uiFileExtensions.some(fileExtension => { + if (attachment.name.replace(/\.(pgp|gpg|asc)$/i, '').endsWith(fileExtension)) { + attachmentFileTypeIcon.attr('src', messenger.runtime.getURL(`/img/fileformat/${fileExtension}.png`)); + } + }); + const attachmentFilename = $('
').addClass('thunderbird_attachment_name').text(attachment.name); const attachmentDownloadBtn = $('
') .addClass('thunderbird_attachment_download') .on('click', async () => { await this.downloadThunderbirdAttachmentHandler(attachment); - }); + }) + .prepend($('').attr('src', messenger.runtime.getURL('/img/svgs/download-link.svg'))); // xss-safe-value attachmentHtmlRoot.append(attachmentFileTypeIcon); // xss-escaped - attachmentHtmlRoot.append(attachmentFilename); // xss-escaped - attachmentHtmlRoot.append(attachmentDownloadBtn); // xss-escaped + attachmentHtmlRoot.append(attachmentFilename); // xss-safe-value + attachmentHtmlRoot.append(attachmentDownloadBtn); // xss-safe-value return attachmentHtmlRoot; }; From 409f2ad037ea07816da9bd5c88f09e8b891d72a9 Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 18 Oct 2024 17:40:34 +0800 Subject: [PATCH 13/32] simplified message detection --- .../thunderbird-element-replacer.ts | 111 ++++++++---------- 1 file changed, 50 insertions(+), 61 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 18f27112a88..557f4a8f907 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -21,7 +21,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void; private acctEmail: string; private emailBodyFromThunderbirdMail: string; - private thunderbirdEmailSelector = $('div.moz-text-plain'); + private emailBodyToParse = $('div.moz-text-plain').text().trim(); public getIntervalFunctions = (): IntervalFunction[] => { return [{ interval: 2000, handler: () => this.replaceThunderbirdMsgPane() }]; @@ -29,61 +29,61 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public replaceThunderbirdMsgPane = async () => { if (Catch.isThunderbirdMail()) { - const { messagePart, attachments } = await BrowserMsg.send.bg.await.thunderbirdMsgGet(); - if (messagePart) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.acctEmail = (await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser())!; - const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, this.acctEmail))?.sortedPubkeys ?? []; - const signerKeys = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); - if (this.isPublicKeyEncryptedMsg()) { - const result = await MsgUtil.decryptMessage({ - kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), - encryptedData: this.emailBodyFromThunderbirdMail, - verificationPubs: signerKeys, - }); - if (result.success && result.content) { - const decryptedMsg = result.content.toUtfStr(); - const encryptionStatus = result.isEncrypted ? 'encrypted' : 'not encrypted'; - let verificationStatus = ''; - if (result?.signature) { - if (result.signature.match) { - verificationStatus = 'signed'; - } else if (result.signature.error) { - verificationStatus = `could not verify signature: ${result.signature.error}`; - } else { - verificationStatus = 'not signed'; - } - } - const pgpBlock = this.generatePgpBlockTemplate(encryptionStatus, verificationStatus, decryptedMsg); - $('body').html(pgpBlock); // xss-sanitized - } else { - const decryptErr = result as DecryptError; - let decryptionErrorMsg = ''; - if (decryptErr.error && decryptErr.error.type === DecryptErrTypes.needPassphrase) { - const acctEmail = String(await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser()); - const longids = decryptErr.longids.needPassphrase.join(','); - decryptionErrorMsg = `decrypt error: private key needs to be unlocked by your passphrase.`; - await BrowserMsg.send.bg.await.thunderbirdOpenPassphraseDiaglog({ acctEmail, longids }); + const { attachments } = await BrowserMsg.send.bg.await.thunderbirdMsgGet(); + const pgpRegex = /-----BEGIN PGP MESSAGE-----(.*?)-----END PGP MESSAGE-----/s; + const pgpRegexMatch = new RegExp(pgpRegex).exec(this.emailBodyToParse); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.acctEmail = (await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser())!; + const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, this.acctEmail))?.sortedPubkeys ?? []; + const signerKeys = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); + if (pgpRegexMatch && this.resemblesAsciiArmoredMsg(pgpRegexMatch[0])) { + const result = await MsgUtil.decryptMessage({ + kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), + encryptedData: this.emailBodyFromThunderbirdMail, + verificationPubs: signerKeys, + }); + if (result.success && result.content) { + const decryptedMsg = result.content.toUtfStr(); + const encryptionStatus = result.isEncrypted ? 'encrypted' : 'not encrypted'; + let verificationStatus = ''; + if (result?.signature) { + if (result.signature.match) { + verificationStatus = 'signed'; + } else if (result.signature.error) { + verificationStatus = `could not verify signature: ${result.signature.error}`; } else { - decryptionErrorMsg = `decrypt error: ${(result as DecryptError).error.message}`; + verificationStatus = 'not signed'; } - const pgpBlock = this.generatePgpBlockTemplate(decryptionErrorMsg, 'not signed', this.emailBodyFromThunderbirdMail); - $('body').html(pgpBlock); // xss-sanitized } - } else if (this.isCleartextMsg()) { - const message = await openpgp.readCleartextMessage({ cleartextMessage: this.emailBodyFromThunderbirdMail }); - const result = await OpenPGPKey.verify(message, await ContactStore.getPubkeyInfos(undefined, signerKeys)); - let verificationStatus = ''; - let signedMessage = ''; - if (result.match && result.content) { - verificationStatus = 'signed'; - signedMessage = result.content.toUtfStr(); - } else if (result.error) { - verificationStatus = `could not verify signature: ${result.error}`; + const pgpBlock = this.generatePgpBlockTemplate(encryptionStatus, verificationStatus, decryptedMsg); + $('body').html(pgpBlock); // xss-sanitized + } else { + const decryptErr = result as DecryptError; + let decryptionErrorMsg = ''; + if (decryptErr.error && decryptErr.error.type === DecryptErrTypes.needPassphrase) { + const acctEmail = String(await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser()); + const longids = decryptErr.longids.needPassphrase.join(','); + decryptionErrorMsg = `decrypt error: private key needs to be unlocked by your passphrase.`; + await BrowserMsg.send.bg.await.thunderbirdOpenPassphraseDiaglog({ acctEmail, longids }); + } else { + decryptionErrorMsg = `decrypt error: ${(result as DecryptError).error.message}`; } - const pgpBlock = this.generatePgpBlockTemplate('not encrypted', verificationStatus, signedMessage); + const pgpBlock = this.generatePgpBlockTemplate(decryptionErrorMsg, 'not signed', this.emailBodyFromThunderbirdMail); $('body').html(pgpBlock); // xss-sanitized } + } else if (this.resemblesCleartextMsg(this.emailBodyToParse)) { + const message = await openpgp.readCleartextMessage({ cleartextMessage: this.emailBodyFromThunderbirdMail }); + const result = await OpenPGPKey.verify(message, await ContactStore.getPubkeyInfos(undefined, signerKeys)); + let verificationStatus = ''; + let signedMessage = ''; + if (result.match && result.content) { + verificationStatus = 'signed'; + signedMessage = result.content.toUtfStr(); + } else if (result.error) { + verificationStatus = `could not verify signature: ${result.error}`; + } + const pgpBlock = this.generatePgpBlockTemplate('not encrypted', verificationStatus, signedMessage); + $('body').html(pgpBlock); // xss-sanitized } if (attachments.length) { for (const attachment of attachments) { @@ -152,11 +152,6 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { } }; - private isCleartextMsg = (): boolean => { - const emailBody = this.thunderbirdEmailSelector.text().trim(); - return this.resemblesCleartextMsg(emailBody); - }; - private resemblesCleartextMsg = (body: string) => { this.emailBodyFromThunderbirdMail = body; return ( @@ -166,12 +161,6 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { ); }; - private isPublicKeyEncryptedMsg = (): boolean => { - // todo - recognized email sent via FlowCrypt encrypted contact pages - const emailBody = this.thunderbirdEmailSelector.text().trim(); - return this.resemblesAsciiArmoredMsg(emailBody); - }; - private resemblesAsciiArmoredMsg = (body: string): boolean => { this.emailBodyFromThunderbirdMail = body; return body.startsWith(PgpArmor.ARMOR_HEADER_DICT.encryptedMsg.begin) && body.endsWith(PgpArmor.ARMOR_HEADER_DICT.encryptedMsg.end as string); From 463d454ab8f8fac0db90a9ac5a0e098311760592 Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 18 Oct 2024 17:41:22 +0800 Subject: [PATCH 14/32] wip --- .../webmail/thunderbird/thunderbird-element-replacer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 557f4a8f907..2dcdbc8a54e 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -129,7 +129,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { .on('click', async () => { await this.downloadThunderbirdAttachmentHandler(attachment); }) - .prepend($('').attr('src', messenger.runtime.getURL('/img/svgs/download-link.svg'))); // xss-safe-value + .append($('').attr('src', messenger.runtime.getURL('/img/svgs/download-link.svg'))); // xss-safe-value attachmentHtmlRoot.append(attachmentFileTypeIcon); // xss-escaped attachmentHtmlRoot.append(attachmentFilename); // xss-safe-value attachmentHtmlRoot.append(attachmentDownloadBtn); // xss-safe-value From 886f932635c09cadf636cf79ab67bb1971b9c6ed Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 18 Oct 2024 18:36:51 +0800 Subject: [PATCH 15/32] remove unneeded todo --- .../webmail/thunderbird/thunderbird-element-replacer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 2dcdbc8a54e..13db4434a40 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -91,7 +91,6 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment); $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized } - // todo: detect encrypted message send as attachment and render it when email body is empty // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 } } From 449aa3043f25f0dbe652f5d94db114f6e5feaba3 Mon Sep 17 00:00:00 2001 From: martgil Date: Mon, 21 Oct 2024 18:11:08 +0800 Subject: [PATCH 16/32] Add additional support to: encryptedMsg and detached message attachments --- .../thunderbird-element-replacer.ts | 142 +++++++++++------- 1 file changed, 86 insertions(+), 56 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 13db4434a40..22314eafe03 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -3,8 +3,10 @@ 'use strict'; import { BrowserMsg } from '../../../common/browser/browser-msg.js'; +import { Attachment } from '../../../common/core/attachment.js'; +import { Buf } from '../../../common/core/buf.js'; import { KeyUtil } from '../../../common/core/crypto/key.js'; -import { DecryptError, DecryptErrTypes, MsgUtil } from '../../../common/core/crypto/pgp/msg-util.js'; +import { DecryptError, DecryptErrTypes, MsgUtil, VerifyRes } from '../../../common/core/crypto/pgp/msg-util.js'; import { OpenPGPKey } from '../../../common/core/crypto/pgp/openpgp-key.js'; import { PgpArmor } from '../../../common/core/crypto/pgp/pgp-armor'; import { Catch } from '../../../common/platform/catch'; @@ -21,82 +23,109 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void; private acctEmail: string; private emailBodyFromThunderbirdMail: string; - private emailBodyToParse = $('div.moz-text-plain').text().trim(); public getIntervalFunctions = (): IntervalFunction[] => { return [{ interval: 2000, handler: () => this.replaceThunderbirdMsgPane() }]; }; public replaceThunderbirdMsgPane = async () => { + const emailBodyToParse = $('div.moz-text-plain').text().trim() || $('div.moz-text-html').text().trim(); + console.log(emailBodyToParse); if (Catch.isThunderbirdMail()) { const { attachments } = await BrowserMsg.send.bg.await.thunderbirdMsgGet(); const pgpRegex = /-----BEGIN PGP MESSAGE-----(.*?)-----END PGP MESSAGE-----/s; - const pgpRegexMatch = new RegExp(pgpRegex).exec(this.emailBodyToParse); + const pgpRegexMatch = new RegExp(pgpRegex).exec(emailBodyToParse); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.acctEmail = (await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser())!; const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, this.acctEmail))?.sortedPubkeys ?? []; const signerKeys = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); if (pgpRegexMatch && this.resemblesAsciiArmoredMsg(pgpRegexMatch[0])) { - const result = await MsgUtil.decryptMessage({ - kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), - encryptedData: this.emailBodyFromThunderbirdMail, - verificationPubs: signerKeys, - }); - if (result.success && result.content) { - const decryptedMsg = result.content.toUtfStr(); - const encryptionStatus = result.isEncrypted ? 'encrypted' : 'not encrypted'; - let verificationStatus = ''; - if (result?.signature) { - if (result.signature.match) { - verificationStatus = 'signed'; - } else if (result.signature.error) { - verificationStatus = `could not verify signature: ${result.signature.error}`; - } else { - verificationStatus = 'not signed'; - } - } - const pgpBlock = this.generatePgpBlockTemplate(encryptionStatus, verificationStatus, decryptedMsg); - $('body').html(pgpBlock); // xss-sanitized - } else { - const decryptErr = result as DecryptError; - let decryptionErrorMsg = ''; - if (decryptErr.error && decryptErr.error.type === DecryptErrTypes.needPassphrase) { - const acctEmail = String(await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser()); - const longids = decryptErr.longids.needPassphrase.join(','); - decryptionErrorMsg = `decrypt error: private key needs to be unlocked by your passphrase.`; - await BrowserMsg.send.bg.await.thunderbirdOpenPassphraseDiaglog({ acctEmail, longids }); - } else { - decryptionErrorMsg = `decrypt error: ${(result as DecryptError).error.message}`; - } - const pgpBlock = this.generatePgpBlockTemplate(decryptionErrorMsg, 'not signed', this.emailBodyFromThunderbirdMail); - $('body').html(pgpBlock); // xss-sanitized - } - } else if (this.resemblesCleartextMsg(this.emailBodyToParse)) { - const message = await openpgp.readCleartextMessage({ cleartextMessage: this.emailBodyFromThunderbirdMail }); - const result = await OpenPGPKey.verify(message, await ContactStore.getPubkeyInfos(undefined, signerKeys)); - let verificationStatus = ''; - let signedMessage = ''; - if (result.match && result.content) { - verificationStatus = 'signed'; - signedMessage = result.content.toUtfStr(); - } else if (result.error) { - verificationStatus = `could not verify signature: ${result.error}`; - } - const pgpBlock = this.generatePgpBlockTemplate('not encrypted', verificationStatus, signedMessage); - $('body').html(pgpBlock); // xss-sanitized + await this.messageDecrypt(signerKeys, this.emailBodyFromThunderbirdMail); + } else if (this.resemblesSignedMsg(emailBodyToParse)) { + await this.messageVerify(signerKeys); } - if (attachments.length) { + if (emailBodyToParse && attachments.length) { for (const attachment of attachments) { + console.log(attachment.name); if (attachment.name.endsWith('.pgp')) { const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment); $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized + } else if (Attachment.encryptedMsgNames.some(a => attachment.name.includes(a)) && !this.emailBodyFromThunderbirdMail) { + const attachmentData = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); + if (attachmentData) { + await this.messageDecrypt(signerKeys, attachmentData); + } + } else if (attachment.name.endsWith('.asc')) { + const attachmentData = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); + const sigText = new TextDecoder('utf-8').decode(attachmentData).trim(); + if (attachmentData && this.resemblesSignedMsg(sigText)) { + const plaintext = emailBodyToParse; + await this.messageVerify(signerKeys, { plaintext, sigText }); + } } - // todo: detached signed message via https://github.com/FlowCrypt/flowcrypt-browser/issues/5668 } } } }; + private messageDecrypt = async (verificationPubs: string[], encryptedData: string | Buf) => { + const result = await MsgUtil.decryptMessage({ + kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), + encryptedData, + verificationPubs, + }); + if (result.success && result.content) { + const decryptedMsg = result.content.toUtfStr(); + const encryptionStatus = result.isEncrypted ? 'encrypted' : 'not encrypted'; + let verificationStatus = ''; + if (result?.signature) { + if (result.signature.match) { + verificationStatus = 'signed'; + } else if (result.signature.error) { + verificationStatus = `could not verify signature: ${result.signature.error}`; + } else { + verificationStatus = 'not signed'; + } + } + const pgpBlock = this.generatePgpBlockTemplate(encryptionStatus, verificationStatus, decryptedMsg); + $('body').html(pgpBlock); // xss-sanitized + } else { + const decryptErr = result as DecryptError; + let decryptionErrorMsg = ''; + if (decryptErr.error && decryptErr.error.type === DecryptErrTypes.needPassphrase) { + const acctEmail = String(await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser()); + const longids = decryptErr.longids.needPassphrase.join(','); + decryptionErrorMsg = `decrypt error: private key needs to be unlocked by your passphrase.`; + await BrowserMsg.send.bg.await.thunderbirdOpenPassphraseDiaglog({ acctEmail, longids }); + } else { + decryptionErrorMsg = `decrypt error: ${(result as DecryptError).error.message}`; + } + const pgpBlock = this.generatePgpBlockTemplate(decryptionErrorMsg, 'not signed', this.emailBodyFromThunderbirdMail); + $('body').html(pgpBlock); // xss-sanitized + } + }; + + private messageVerify = async (verificationPubs: string[], detachedSignatureParams?: { plaintext: string; sigText: string }) => { + let result: VerifyRes; + if (!detachedSignatureParams) { + const message = await openpgp.readCleartextMessage({ cleartextMessage: this.emailBodyFromThunderbirdMail }); + result = await OpenPGPKey.verify(message, await ContactStore.getPubkeyInfos(undefined, verificationPubs)); + } else { + result = await MsgUtil.verifyDetached({ plaintext: detachedSignatureParams.plaintext, sigText: detachedSignatureParams.sigText, verificationPubs }); + } + let verificationStatus = ''; + let signedMessage = ''; + if (result.match && result.content) { + verificationStatus = 'signed'; + signedMessage = result.content.toUtfStr(); + } else if (result.error) { + verificationStatus = `could not verify signature: ${result.error}`; + signedMessage = detachedSignatureParams?.plaintext || ''; + } + const pgpBlock = this.generatePgpBlockTemplate('not encrypted', verificationStatus, signedMessage); + $('body').html(pgpBlock); // xss-sanitized + }; + private generatePgpBlockTemplate = (encryptionStatus: string, verificationStatus: string, messageToRender: string): string => { return `
@@ -151,12 +180,13 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { } }; - private resemblesCleartextMsg = (body: string) => { + private resemblesSignedMsg = (body: string) => { this.emailBodyFromThunderbirdMail = body; return ( - body.startsWith(PgpArmor.ARMOR_HEADER_DICT.signedMsg.begin) && - body.includes(String(PgpArmor.ARMOR_HEADER_DICT.signedMsg.middle)) && - body.endsWith(String(PgpArmor.ARMOR_HEADER_DICT.signedMsg.end)) + (body.startsWith(PgpArmor.ARMOR_HEADER_DICT.signedMsg.begin) && + body.includes(String(PgpArmor.ARMOR_HEADER_DICT.signedMsg.middle)) && + body.endsWith(String(PgpArmor.ARMOR_HEADER_DICT.signedMsg.end))) || + (body.startsWith(PgpArmor.ARMOR_HEADER_DICT.signature.begin) && body.endsWith(String(PgpArmor.ARMOR_HEADER_DICT.signature.end))) ); }; From ab132c93546f47d4b430d2bef072f1336f441652 Mon Sep 17 00:00:00 2001 From: martgil Date: Mon, 21 Oct 2024 18:50:36 +0800 Subject: [PATCH 17/32] refactor: remove redundancy --- .../thunderbird-element-replacer.ts | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 22314eafe03..b4a58e02028 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -30,7 +30,6 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public replaceThunderbirdMsgPane = async () => { const emailBodyToParse = $('div.moz-text-plain').text().trim() || $('div.moz-text-html').text().trim(); - console.log(emailBodyToParse); if (Catch.isThunderbirdMail()) { const { attachments } = await BrowserMsg.send.bg.await.thunderbirdMsgGet(); const pgpRegex = /-----BEGIN PGP MESSAGE-----(.*?)-----END PGP MESSAGE-----/s; @@ -46,21 +45,19 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { } if (emailBodyToParse && attachments.length) { for (const attachment of attachments) { - console.log(attachment.name); - if (attachment.name.endsWith('.pgp')) { - const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment); - $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized - } else if (Attachment.encryptedMsgNames.some(a => attachment.name.includes(a)) && !this.emailBodyFromThunderbirdMail) { - const attachmentData = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); - if (attachmentData) { - await this.messageDecrypt(signerKeys, attachmentData); - } - } else if (attachment.name.endsWith('.asc')) { - const attachmentData = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); - const sigText = new TextDecoder('utf-8').decode(attachmentData).trim(); - if (attachmentData && this.resemblesSignedMsg(sigText)) { - const plaintext = emailBodyToParse; - await this.messageVerify(signerKeys, { plaintext, sigText }); + const fcAttachment = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); + if (fcAttachment) { + if (attachment.name.endsWith('.pgp')) { + const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment.name, fcAttachment); + $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized + } else if (Attachment.encryptedMsgNames.some(a => attachment.name.includes(a)) && !this.emailBodyFromThunderbirdMail) { + await this.messageDecrypt(signerKeys, fcAttachment); + } else if (attachment.name.endsWith('.asc')) { + const sigText = new TextDecoder('utf-8').decode(fcAttachment).trim(); + if (this.resemblesSignedMsg(sigText)) { + const plaintext = emailBodyToParse; + await this.messageVerify(signerKeys, { plaintext, sigText }); + } } } } @@ -141,21 +138,21 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer {
`; }; - private generatePgpAttachmentTemplate = (attachment: messenger.messages.MessageAttachment) => { + private generatePgpAttachmentTemplate = (originalFilename: string, attachmentData: Buf) => { const uiFileExtensions = ['excel', 'word', 'png', 'jpg', 'generic']; const attachmentHtmlRoot = $('
').addClass('thunderbird_attachment_root'); const attachmentFileTypeIcon = $('').addClass('thunderbird_attachment_icon'); - attachmentFileTypeIcon.attr('alt', Xss.escape(attachment.name)); + attachmentFileTypeIcon.attr('alt', Xss.escape(originalFilename)); uiFileExtensions.some(fileExtension => { - if (attachment.name.replace(/\.(pgp|gpg|asc)$/i, '').endsWith(fileExtension)) { + if (originalFilename.replace(/\.(pgp|gpg|asc)$/i, '').endsWith(fileExtension)) { attachmentFileTypeIcon.attr('src', messenger.runtime.getURL(`/img/fileformat/${fileExtension}.png`)); } }); - const attachmentFilename = $('
').addClass('thunderbird_attachment_name').text(attachment.name); + const attachmentFilename = $('
').addClass('thunderbird_attachment_name').text(originalFilename); const attachmentDownloadBtn = $('
') .addClass('thunderbird_attachment_download') .on('click', async () => { - await this.downloadThunderbirdAttachmentHandler(attachment); + await this.downloadThunderbirdAttachmentHandler(originalFilename, attachmentData); }) .append($('').attr('src', messenger.runtime.getURL('/img/svgs/download-link.svg'))); // xss-safe-value attachmentHtmlRoot.append(attachmentFileTypeIcon); // xss-escaped @@ -164,8 +161,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { return attachmentHtmlRoot; }; - private downloadThunderbirdAttachmentHandler = async (attachment: messenger.messages.MessageAttachment) => { - const encryptedData = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); + private downloadThunderbirdAttachmentHandler = async (originalFilename: string, encryptedData: Buf) => { if (encryptedData) { const result = await MsgUtil.decryptMessage({ kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), @@ -173,7 +169,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { verificationPubs: [], // todo: #4158 signature verification of attachments }); if (result.success && result.content) { - const decryptedFileName = attachment.name.replace(/\.(pgp|gpg|asc)$/i, ''); + const decryptedFileName = originalFilename.replace(/\.(pgp|gpg|asc)$/i, ''); const decryptedContent = result.content; await BrowserMsg.send.bg.await.thunderbirdInitiateAttachmentDownload({ decryptedFileName, decryptedContent }); } From e25fb011bad0bf8f06a04b4f115056b099185fd0 Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 24 Oct 2024 11:51:13 +0800 Subject: [PATCH 18/32] cleanup --- .../thunderbird-element-replacer.ts | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index b4a58e02028..4f134759145 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -47,18 +47,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { for (const attachment of attachments) { const fcAttachment = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); if (fcAttachment) { - if (attachment.name.endsWith('.pgp')) { - const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachment.name, fcAttachment); - $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized - } else if (Attachment.encryptedMsgNames.some(a => attachment.name.includes(a)) && !this.emailBodyFromThunderbirdMail) { - await this.messageDecrypt(signerKeys, fcAttachment); - } else if (attachment.name.endsWith('.asc')) { - const sigText = new TextDecoder('utf-8').decode(fcAttachment).trim(); - if (this.resemblesSignedMsg(sigText)) { - const plaintext = emailBodyToParse; - await this.messageVerify(signerKeys, { plaintext, sigText }); - } - } + await this.attachmentUiRenderer(attachment.name, fcAttachment, signerKeys, emailBodyToParse); } } } @@ -123,6 +112,20 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { $('body').html(pgpBlock); // xss-sanitized }; + private attachmentUiRenderer = async (attachmentName: string, fcAttachment: Buf, verificationPubs: string[], plaintext: string) => { + if (attachmentName.endsWith('.pgp')) { + const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachmentName, fcAttachment); + $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized + } else if (Attachment.encryptedMsgNames.some(a => attachmentName.includes(a)) && !this.emailBodyFromThunderbirdMail) { + await this.messageDecrypt(verificationPubs, fcAttachment); + } else if (attachmentName.endsWith('.asc')) { + const sigText = new TextDecoder('utf-8').decode(fcAttachment).trim(); + if (this.resemblesSignedMsg(sigText)) { + await this.messageVerify(verificationPubs, { plaintext, sigText }); + } + } + }; + private generatePgpBlockTemplate = (encryptionStatus: string, verificationStatus: string, messageToRender: string): string => { return `
@@ -142,9 +145,9 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { const uiFileExtensions = ['excel', 'word', 'png', 'jpg', 'generic']; const attachmentHtmlRoot = $('
').addClass('thunderbird_attachment_root'); const attachmentFileTypeIcon = $('').addClass('thunderbird_attachment_icon'); - attachmentFileTypeIcon.attr('alt', Xss.escape(originalFilename)); + const decryptedFileName = originalFilename.replace(/\.(pgp|gpg|asc)$/i, ''); uiFileExtensions.some(fileExtension => { - if (originalFilename.replace(/\.(pgp|gpg|asc)$/i, '').endsWith(fileExtension)) { + if (decryptedFileName.endsWith(fileExtension)) { attachmentFileTypeIcon.attr('src', messenger.runtime.getURL(`/img/fileformat/${fileExtension}.png`)); } }); @@ -152,7 +155,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { const attachmentDownloadBtn = $('
') .addClass('thunderbird_attachment_download') .on('click', async () => { - await this.downloadThunderbirdAttachmentHandler(originalFilename, attachmentData); + await this.downloadThunderbirdAttachmentHandler(decryptedFileName, attachmentData); }) .append($('').attr('src', messenger.runtime.getURL('/img/svgs/download-link.svg'))); // xss-safe-value attachmentHtmlRoot.append(attachmentFileTypeIcon); // xss-escaped @@ -161,7 +164,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { return attachmentHtmlRoot; }; - private downloadThunderbirdAttachmentHandler = async (originalFilename: string, encryptedData: Buf) => { + private downloadThunderbirdAttachmentHandler = async (decryptedFileName: string, encryptedData: Buf) => { if (encryptedData) { const result = await MsgUtil.decryptMessage({ kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), @@ -169,9 +172,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { verificationPubs: [], // todo: #4158 signature verification of attachments }); if (result.success && result.content) { - const decryptedFileName = originalFilename.replace(/\.(pgp|gpg|asc)$/i, ''); - const decryptedContent = result.content; - await BrowserMsg.send.bg.await.thunderbirdInitiateAttachmentDownload({ decryptedFileName, decryptedContent }); + await BrowserMsg.send.bg.await.thunderbirdInitiateAttachmentDownload({ decryptedFileName, decryptedContent: result.content }); } } }; From 85cf5f99bcdd1bb43195fe4c9eadca49a92604f9 Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 24 Oct 2024 13:32:58 +0800 Subject: [PATCH 19/32] added reliable attachment type recognition --- extension/js/common/browser/browser-msg.ts | 5 ++-- extension/js/common/core/attachment.ts | 6 +++++ .../thunderbird-element-replacer.ts | 27 ++++++++++--------- extension/js/service_worker/bg-handlers.ts | 27 ++++++++++++++++--- 4 files changed, 46 insertions(+), 19 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index 4c566caa5c1..c72f4a0c937 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -20,6 +20,7 @@ import { Ui } from './ui.js'; import { AuthRes } from '../api/authentication/generic/oauth.js'; import { GlobalStore } from '../platform/store/global-store.js'; import { BgUtils } from '../../service_worker/bgutils.js'; +import { ThunderbirdAttachment } from '../core/attachment.js'; export type GoogleAuthWindowResult$result = 'Success' | 'Denied' | 'Error' | 'Closed'; export type ScreenDimensions = { width: number; height: number; availLeft: number; availTop: number }; @@ -98,7 +99,7 @@ export namespace Bm { export type PgpBlockRetry = { frameId: string; messageSender: Dest }; export type PgpBlockReady = { frameId: string; messageSender: Dest }; export type ThunderbirdOpenPassphraseDialog = { acctEmail: string; longids: string }; - export type ThunderbirdGetDownloadableAttachment = { attachment: messenger.messages.MessageAttachment }; + export type ThunderbirdGetDownloadableAttachment = { attachments: messenger.messages.MessageAttachment[] }; export type ThunderbirdInitiateAttachmentDownload = { decryptedFileName: string; decryptedContent: Buf }; export namespace Res { @@ -116,7 +117,7 @@ export namespace Bm { export type ExpirationCacheGet = Promise; export type ExpirationCacheSet = Promise; export type ExpirationCacheDeleteExpired = Promise; - export type ThunderbirdGetDownloadableAttachment = Buf | undefined; + export type ThunderbirdGetDownloadableAttachment = ThunderbirdAttachment[]; export type ThunderbirdGetCurrentUser = string | undefined; export type ThunderbirdMsgGet = { attachments: messenger.messages.MessageAttachment[]; messagePart: messenger.messages.MessagePart }; export type ThunderbirdOpenPassphraseDialog = Promise; diff --git a/extension/js/common/core/attachment.ts b/extension/js/common/core/attachment.ts index 25909141051..f907c2cfb92 100644 --- a/extension/js/common/core/attachment.ts +++ b/extension/js/common/core/attachment.ts @@ -29,6 +29,12 @@ export type AttachmentProperties = { contentDescription?: string; contentTransferEncoding?: ContentTransferEncoding; }; +export type ThunderbirdAttachment = { + name: string; + contentType: string; + data: Buf; + treatAs: Attachment$treatAs; +}; export type AttachmentMeta = (AttachmentId | { data: Uint8Array }) & AttachmentProperties; export type FcAttachmentLinkData = { name: string; type: string; size: number }; diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 4f134759145..2186c300f30 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -3,7 +3,7 @@ 'use strict'; import { BrowserMsg } from '../../../common/browser/browser-msg.js'; -import { Attachment } from '../../../common/core/attachment.js'; +import { Attachment, ThunderbirdAttachment } from '../../../common/core/attachment.js'; import { Buf } from '../../../common/core/buf.js'; import { KeyUtil } from '../../../common/core/crypto/key.js'; import { DecryptError, DecryptErrTypes, MsgUtil, VerifyRes } from '../../../common/core/crypto/pgp/msg-util.js'; @@ -44,11 +44,9 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { await this.messageVerify(signerKeys); } if (emailBodyToParse && attachments.length) { - for (const attachment of attachments) { - const fcAttachment = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachment }); - if (fcAttachment) { - await this.attachmentUiRenderer(attachment.name, fcAttachment, signerKeys, emailBodyToParse); - } + const fcAttachments = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachments }); + for (const fcAttachment of fcAttachments) { + await this.attachmentUiRenderer(fcAttachment, signerKeys, emailBodyToParse); } } } @@ -112,14 +110,17 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { $('body').html(pgpBlock); // xss-sanitized }; - private attachmentUiRenderer = async (attachmentName: string, fcAttachment: Buf, verificationPubs: string[], plaintext: string) => { - if (attachmentName.endsWith('.pgp')) { - const generatedPgpTemplate = this.generatePgpAttachmentTemplate(attachmentName, fcAttachment); + private attachmentUiRenderer = async (fcAttachment: ThunderbirdAttachment, verificationPubs: string[], plaintext: string) => { + if (fcAttachment.treatAs === 'encryptedFile') { + const generatedPgpTemplate = this.generatePgpAttachmentTemplate(fcAttachment.name, fcAttachment.data); $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized - } else if (Attachment.encryptedMsgNames.some(a => attachmentName.includes(a)) && !this.emailBodyFromThunderbirdMail) { - await this.messageDecrypt(verificationPubs, fcAttachment); - } else if (attachmentName.endsWith('.asc')) { - const sigText = new TextDecoder('utf-8').decode(fcAttachment).trim(); + } else if ( + (fcAttachment.treatAs === 'encryptedMsg' || Attachment.encryptedMsgNames.some(a => fcAttachment.name.includes(a))) && + !this.emailBodyFromThunderbirdMail + ) { + await this.messageDecrypt(verificationPubs, fcAttachment.data); + } else if (fcAttachment.treatAs === 'signature') { + const sigText = new TextDecoder('utf-8').decode(fcAttachment.data).trim(); if (this.resemblesSignedMsg(sigText)) { await this.messageVerify(verificationPubs, { plaintext, sigText }); } diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index d45a3570a9e..43cc782ad96 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -166,13 +166,32 @@ export class BgHandlers { public static thunderbirdGetDownloadableAttachment = async ( r: Bm.ThunderbirdGetDownloadableAttachment ): Promise => { + const processableAttachments: Bm.Res.ThunderbirdGetDownloadableAttachment = []; const [tab] = await messenger.mailTabs.query({ active: true, currentWindow: true }); if (tab.id) { - const rawAttachment = await messenger.messages.getAttachmentFile(tab.id, r.attachment.partName); - const data = new Uint8Array(await rawAttachment.arrayBuffer()); - return new Attachment({ data }).getData(); + const fcAttachments: Attachment[] = []; + // convert Thunderbird Attachments to FlowCrypt recognizable Attachments + for (const tbAttachment of r.attachments) { + const rawAttachment = await messenger.messages.getAttachmentFile(tab.id, tbAttachment.partName); + fcAttachments.push( + new Attachment({ + data: new Uint8Array(await rawAttachment.arrayBuffer()), + type: tbAttachment.contentType, + name: tbAttachment.name, + length: tbAttachment.size, + }) + ); + } + for (const fcAttachment of fcAttachments) { + processableAttachments.push({ + name: fcAttachment.name, + contentType: fcAttachment.type, + data: fcAttachment.getData(), + treatAs: fcAttachment.treatAs(fcAttachments), + }); + } } - return; + return processableAttachments; }; public static thunderbirdInitiateAttachmentDownload = async ( From 7eae082b8487f65427ca46bc4c2622d9cee1a272 Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 24 Oct 2024 14:13:25 +0800 Subject: [PATCH 20/32] cleanup --- .../thunderbird/thunderbird-element-replacer.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 2186c300f30..9b5f282fb78 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -43,7 +43,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { } else if (this.resemblesSignedMsg(emailBodyToParse)) { await this.messageVerify(signerKeys); } - if (emailBodyToParse && attachments.length) { + if (attachments.length) { const fcAttachments = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachments }); for (const fcAttachment of fcAttachments) { await this.attachmentUiRenderer(fcAttachment, signerKeys, emailBodyToParse); @@ -59,7 +59,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { verificationPubs, }); if (result.success && result.content) { - const decryptedMsg = result.content.toUtfStr(); + const decryptedContent = result.content.toUtfStr(); const encryptionStatus = result.isEncrypted ? 'encrypted' : 'not encrypted'; let verificationStatus = ''; if (result?.signature) { @@ -71,7 +71,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { verificationStatus = 'not signed'; } } - const pgpBlock = this.generatePgpBlockTemplate(encryptionStatus, verificationStatus, decryptedMsg); + const pgpBlock = this.generatePgpBlockTemplate(encryptionStatus, verificationStatus, decryptedContent); $('body').html(pgpBlock); // xss-sanitized } else { const decryptErr = result as DecryptError; @@ -110,8 +110,8 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { $('body').html(pgpBlock); // xss-sanitized }; - private attachmentUiRenderer = async (fcAttachment: ThunderbirdAttachment, verificationPubs: string[], plaintext: string) => { - if (fcAttachment.treatAs === 'encryptedFile') { + private attachmentUiRenderer = async (fcAttachment: ThunderbirdAttachment, verificationPubs: string[], emailBodyToParse: string) => { + if (fcAttachment.treatAs === 'encryptedFile' && emailBodyToParse) { const generatedPgpTemplate = this.generatePgpAttachmentTemplate(fcAttachment.name, fcAttachment.data); $('.pgp_attachments_block').append(generatedPgpTemplate); // xss-sanitized } else if ( @@ -122,7 +122,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { } else if (fcAttachment.treatAs === 'signature') { const sigText = new TextDecoder('utf-8').decode(fcAttachment.data).trim(); if (this.resemblesSignedMsg(sigText)) { - await this.messageVerify(verificationPubs, { plaintext, sigText }); + await this.messageVerify(verificationPubs, { plaintext: emailBodyToParse, sigText }); } } }; @@ -175,6 +175,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { if (result.success && result.content) { await BrowserMsg.send.bg.await.thunderbirdInitiateAttachmentDownload({ decryptedFileName, decryptedContent: result.content }); } + // no need to handle DecryptErrTypes.needPassphrase it was already handled by this.messageDecrypt() } }; From ae3e7bca84f76f4d93a0ee942d66445c5d41d6d0 Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 24 Oct 2024 16:57:55 +0800 Subject: [PATCH 21/32] fix missing icon for generic filetype --- .../thunderbird/thunderbird-element-replacer.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 9b5f282fb78..13674b096ef 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -143,15 +143,16 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; private generatePgpAttachmentTemplate = (originalFilename: string, attachmentData: Buf) => { - const uiFileExtensions = ['excel', 'word', 'png', 'jpg', 'generic']; const attachmentHtmlRoot = $('
').addClass('thunderbird_attachment_root'); const attachmentFileTypeIcon = $('').addClass('thunderbird_attachment_icon'); const decryptedFileName = originalFilename.replace(/\.(pgp|gpg|asc)$/i, ''); - uiFileExtensions.some(fileExtension => { - if (decryptedFileName.endsWith(fileExtension)) { - attachmentFileTypeIcon.attr('src', messenger.runtime.getURL(`/img/fileformat/${fileExtension}.png`)); - } - }); + const uiFileExtensions = ['excel', 'word', 'png', 'jpg']; + const matchedExtension = uiFileExtensions.find(fileExtension => decryptedFileName.endsWith(fileExtension)); + if (matchedExtension) { + attachmentFileTypeIcon.attr('src', messenger.runtime.getURL(`/img/fileformat/${matchedExtension}.png`)); + } else { + attachmentFileTypeIcon.attr('src', messenger.runtime.getURL(`/img/fileformat/generic.png`)); + } const attachmentFilename = $('
').addClass('thunderbird_attachment_name').text(originalFilename); const attachmentDownloadBtn = $('
') .addClass('thunderbird_attachment_download') From f707f6fec2718db4add9abd56a3bd6a6f8e5e4c2 Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 24 Oct 2024 17:10:38 +0800 Subject: [PATCH 22/32] fix incorrect logic for obtaining message attachments --- extension/js/service_worker/bg-handlers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index 43cc782ad96..428da4702a1 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -168,11 +168,12 @@ export class BgHandlers { ): Promise => { const processableAttachments: Bm.Res.ThunderbirdGetDownloadableAttachment = []; const [tab] = await messenger.mailTabs.query({ active: true, currentWindow: true }); - if (tab.id) { + const message = await messenger.messageDisplay.getDisplayedMessage(tab.id); + if (tab.id && message?.id) { const fcAttachments: Attachment[] = []; // convert Thunderbird Attachments to FlowCrypt recognizable Attachments for (const tbAttachment of r.attachments) { - const rawAttachment = await messenger.messages.getAttachmentFile(tab.id, tbAttachment.partName); + const rawAttachment = await messenger.messages.getAttachmentFile(message.id, tbAttachment.partName); fcAttachments.push( new Attachment({ data: new Uint8Array(await rawAttachment.arrayBuffer()), From 67522cf0f837757d4ad0f897025f8b738fb7030b Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 24 Oct 2024 17:20:03 +0800 Subject: [PATCH 23/32] remove code duplicates --- extension/js/common/browser/browser-msg.ts | 6 ++---- .../thunderbird/thunderbird-element-replacer.ts | 9 +++------ extension/js/service_worker/bg-handlers.ts | 17 ++++++++--------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index c72f4a0c937..cdf867c2e96 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -99,7 +99,6 @@ export namespace Bm { export type PgpBlockRetry = { frameId: string; messageSender: Dest }; export type PgpBlockReady = { frameId: string; messageSender: Dest }; export type ThunderbirdOpenPassphraseDialog = { acctEmail: string; longids: string }; - export type ThunderbirdGetDownloadableAttachment = { attachments: messenger.messages.MessageAttachment[] }; export type ThunderbirdInitiateAttachmentDownload = { decryptedFileName: string; decryptedContent: Buf }; export namespace Res { @@ -181,7 +180,6 @@ export namespace Bm { | PgpBlockReady | PgpBlockRetry | ConfirmationResult - | ThunderbirdGetDownloadableAttachment | ThunderbirdOpenPassphraseDialog | ThunderbirdInitiateAttachmentDownload | Ajax; @@ -247,8 +245,8 @@ export class BrowserMsg { BrowserMsg.sendAwait(undefined, 'expirationCacheSet', bm, true) as Promise, expirationCacheDeleteExpired: (bm: Bm.ExpirationCacheDeleteExpired) => BrowserMsg.sendAwait(undefined, 'expirationCacheDeleteExpired', bm, true) as Promise, - thunderbirdGetDownloadableAttachment: (bm: Bm.ThunderbirdGetDownloadableAttachment) => - BrowserMsg.sendAwait(undefined, 'thunderbirdGetDownloadableAttachment', bm, true) as Promise, + thunderbirdGetDownloadableAttachment: () => + BrowserMsg.sendAwait(undefined, 'thunderbirdGetDownloadableAttachment', undefined, true) as Promise, thunderbirdInitiateAttachmentDownload: (bm: Bm.ThunderbirdInitiateAttachmentDownload) => BrowserMsg.sendAwait(undefined, 'thunderbirdInitiateAttachmentDownload', bm, true) as Promise, thunderbirdGetCurrentUser: () => diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 13674b096ef..0772bffc93f 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -31,7 +31,6 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public replaceThunderbirdMsgPane = async () => { const emailBodyToParse = $('div.moz-text-plain').text().trim() || $('div.moz-text-html').text().trim(); if (Catch.isThunderbirdMail()) { - const { attachments } = await BrowserMsg.send.bg.await.thunderbirdMsgGet(); const pgpRegex = /-----BEGIN PGP MESSAGE-----(.*?)-----END PGP MESSAGE-----/s; const pgpRegexMatch = new RegExp(pgpRegex).exec(emailBodyToParse); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -43,11 +42,9 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { } else if (this.resemblesSignedMsg(emailBodyToParse)) { await this.messageVerify(signerKeys); } - if (attachments.length) { - const fcAttachments = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment({ attachments }); - for (const fcAttachment of fcAttachments) { - await this.attachmentUiRenderer(fcAttachment, signerKeys, emailBodyToParse); - } + const fcAttachments = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment(); + for (const fcAttachment of fcAttachments) { + await this.attachmentUiRenderer(fcAttachment, signerKeys, emailBodyToParse); } } }; diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index 428da4702a1..ac0d65ad563 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -163,23 +163,22 @@ export class BgHandlers { }); }; - public static thunderbirdGetDownloadableAttachment = async ( - r: Bm.ThunderbirdGetDownloadableAttachment - ): Promise => { + public static thunderbirdGetDownloadableAttachment = async (): Promise => { const processableAttachments: Bm.Res.ThunderbirdGetDownloadableAttachment = []; const [tab] = await messenger.mailTabs.query({ active: true, currentWindow: true }); const message = await messenger.messageDisplay.getDisplayedMessage(tab.id); if (tab.id && message?.id) { + const attachments = await messenger.messages.listAttachments(message.id); const fcAttachments: Attachment[] = []; // convert Thunderbird Attachments to FlowCrypt recognizable Attachments - for (const tbAttachment of r.attachments) { - const rawAttachment = await messenger.messages.getAttachmentFile(message.id, tbAttachment.partName); + for (const attachment of attachments) { + const file = await messenger.messages.getAttachmentFile(message.id, attachment.partName); fcAttachments.push( new Attachment({ - data: new Uint8Array(await rawAttachment.arrayBuffer()), - type: tbAttachment.contentType, - name: tbAttachment.name, - length: tbAttachment.size, + data: new Uint8Array(await file.arrayBuffer()), + type: attachment.contentType, + name: attachment.name, + length: attachment.size, }) ); } From 8c6555a4a51e1cf3f0dfd98ab8f41465f448f6ac Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 24 Oct 2024 17:29:15 +0800 Subject: [PATCH 24/32] cleanup --- extension/js/common/browser/browser-msg.ts | 5 +---- extension/js/service_worker/background.ts | 1 - extension/js/service_worker/bg-handlers.ts | 13 ------------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index cdf867c2e96..1c87942e5b8 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -118,7 +118,6 @@ export namespace Bm { export type ExpirationCacheDeleteExpired = Promise; export type ThunderbirdGetDownloadableAttachment = ThunderbirdAttachment[]; export type ThunderbirdGetCurrentUser = string | undefined; - export type ThunderbirdMsgGet = { attachments: messenger.messages.MessageAttachment[]; messagePart: messenger.messages.MessagePart }; export type ThunderbirdOpenPassphraseDialog = Promise; export type ThunderbirdInitiateAttachmentDownload = Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -138,8 +137,7 @@ export namespace Bm { | ExpirationCacheDeleteExpired | AjaxGmailAttachmentGetChunk | ConfirmationResult - | ThunderbirdGetDownloadableAttachment - | ThunderbirdMsgGet; + | ThunderbirdGetDownloadableAttachment; } export type AnyRequest = @@ -251,7 +249,6 @@ export class BrowserMsg { BrowserMsg.sendAwait(undefined, 'thunderbirdInitiateAttachmentDownload', bm, true) as Promise, thunderbirdGetCurrentUser: () => BrowserMsg.sendAwait(undefined, 'thunderbirdGetCurrentUser', undefined, true) as Promise, - thunderbirdMsgGet: () => BrowserMsg.sendAwait(undefined, 'thunderbirdMsgGet', undefined, true) as Promise, thunderbirdOpenPassphraseDiaglog: (bm: Bm.ThunderbirdOpenPassphraseDialog) => BrowserMsg.sendAwait(undefined, 'thunderbirdOpenPassphraseDialog', bm, true) as Promise, }, diff --git a/extension/js/service_worker/background.ts b/extension/js/service_worker/background.ts index 34a09c3499b..a8496d5763b 100644 --- a/extension/js/service_worker/background.ts +++ b/extension/js/service_worker/background.ts @@ -74,7 +74,6 @@ console.info('background.js service worker starting'); BgHandlers.thunderbirdSecureComposeHandler(); await BgHandlers.thunderbirdContentScriptRegistration(); BrowserMsg.bgAddListener('thunderbirdGetCurrentUser', BgHandlers.thunderbirdGetCurrentUserHandler); - BrowserMsg.bgAddListener('thunderbirdMsgGet', BgHandlers.thunderbirdMsgGetHandler); BrowserMsg.bgAddListener('thunderbirdGetDownloadableAttachment', BgHandlers.thunderbirdGetDownloadableAttachment); BrowserMsg.bgAddListener('thunderbirdInitiateAttachmentDownload', BgHandlers.thunderbirdInitiateAttachmentDownload); BrowserMsg.bgAddListener('thunderbirdOpenPassphraseDialog', BgHandlers.thunderbirdOpenPassphraseDialog); diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index ac0d65ad563..a185d06b703 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -218,19 +218,6 @@ export class BgHandlers { return; }; - public static thunderbirdMsgGetHandler = async (): Promise => { - const [tab] = await messenger.tabs.query({ active: true, currentWindow: true }); - if (tab.id) { - const message = await messenger.messageDisplay.getDisplayedMessage(tab.id); - if (message?.id) { - const attachments = await messenger.messages.listAttachments(message.id); - const messagePart = await messenger.messages.getFull(message.id); - return { attachments, messagePart }; - } - } - return { attachments: [], messagePart: {} as messenger.messages.MessagePart }; - }; - public static thunderbirdOpenPassphraseDialog = async (r: Bm.ThunderbirdOpenPassphraseDialog): Promise => { await BgUtils.openExtensionTab(`chrome/elements/passphrase.htm?type=message&parentTabId=0&acctEmail=${r.acctEmail}&longids=${r.longids}`, true); }; From 29e9f8217f0557f1dc596a42e84f7ff5e9bfc557 Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 24 Oct 2024 17:37:55 +0800 Subject: [PATCH 25/32] refactor --- .../webmail/thunderbird/thunderbird-element-replacer.ts | 4 ++-- .../webmail/thunderbird/thunderbird-webmail-startup.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 0772bffc93f..1198c824099 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -25,10 +25,10 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { private emailBodyFromThunderbirdMail: string; public getIntervalFunctions = (): IntervalFunction[] => { - return [{ interval: 2000, handler: () => this.replaceThunderbirdMsgPane() }]; + return [{ interval: 2000, handler: () => this.handleThunderbirdMessageParsing() }]; }; - public replaceThunderbirdMsgPane = async () => { + public handleThunderbirdMessageParsing = async () => { const emailBodyToParse = $('div.moz-text-plain').text().trim() || $('div.moz-text-html').text().trim(); if (Catch.isThunderbirdMail()) { const pgpRegex = /-----BEGIN PGP MESSAGE-----(.*?)-----END PGP MESSAGE-----/s; diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts index ae9f803a6db..34b68f7c2dd 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-webmail-startup.ts @@ -19,7 +19,8 @@ export class ThunderbirdWebmailStartup { private start = async () => { this.replacer = new ThunderbirdElementReplacer(); - this.replacer.runIntervalFunctionsPeriodically(); + // doesn't need hearbeat-like content replacer as the extension noticeably slows the Thunderbird client. + await this.replacer.handleThunderbirdMessageParsing(); // todo: show notification using Thunderbird Notification as contentscript notification or such does not work. // await notifications.showInitial(acctEmail); // notifications.show( From e33693f5f88f1251cd203d5ebd55fe71990d96ca Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 24 Oct 2024 18:04:36 +0800 Subject: [PATCH 26/32] cleanup --- .../thunderbird-element-replacer.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 1198c824099..9d6b3c8da86 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -31,25 +31,27 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public handleThunderbirdMessageParsing = async () => { const emailBodyToParse = $('div.moz-text-plain').text().trim() || $('div.moz-text-html').text().trim(); if (Catch.isThunderbirdMail()) { - const pgpRegex = /-----BEGIN PGP MESSAGE-----(.*?)-----END PGP MESSAGE-----/s; - const pgpRegexMatch = new RegExp(pgpRegex).exec(emailBodyToParse); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.acctEmail = (await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser())!; const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, this.acctEmail))?.sortedPubkeys ?? []; const signerKeys = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); - if (pgpRegexMatch && this.resemblesAsciiArmoredMsg(pgpRegexMatch[0])) { + if (this.resemblesAsciiArmoredMsg(emailBodyToParse)) { await this.messageDecrypt(signerKeys, this.emailBodyFromThunderbirdMail); } else if (this.resemblesSignedMsg(emailBodyToParse)) { await this.messageVerify(signerKeys); } const fcAttachments = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment(); - for (const fcAttachment of fcAttachments) { - await this.attachmentUiRenderer(fcAttachment, signerKeys, emailBodyToParse); + if (fcAttachments.length) { + for (const fcAttachment of fcAttachments) { + await this.attachmentUiRenderer(fcAttachment, signerKeys, emailBodyToParse); + } } + $('body').show(); } }; private messageDecrypt = async (verificationPubs: string[], encryptedData: string | Buf) => { + $('body').hide(); const result = await MsgUtil.decryptMessage({ kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), encryptedData, @@ -87,6 +89,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; private messageVerify = async (verificationPubs: string[], detachedSignatureParams?: { plaintext: string; sigText: string }) => { + $('body').hide(); let result: VerifyRes; if (!detachedSignatureParams) { const message = await openpgp.readCleartextMessage({ cleartextMessage: this.emailBodyFromThunderbirdMail }); @@ -188,7 +191,12 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; private resemblesAsciiArmoredMsg = (body: string): boolean => { - this.emailBodyFromThunderbirdMail = body; - return body.startsWith(PgpArmor.ARMOR_HEADER_DICT.encryptedMsg.begin) && body.endsWith(PgpArmor.ARMOR_HEADER_DICT.encryptedMsg.end as string); + const pgpRegex = /-----BEGIN PGP MESSAGE-----(.*?)-----END PGP MESSAGE-----/s; + const pgpRegexMatch = new RegExp(pgpRegex).exec(body); + if (pgpRegexMatch?.[0]) { + this.emailBodyFromThunderbirdMail = pgpRegexMatch[0]; + return true; + } + return false; }; } From b185b2270ea5e9a7adc7fbc51400d976a5e0d048 Mon Sep 17 00:00:00 2001 From: martgil Date: Thu, 24 Oct 2024 18:53:06 +0800 Subject: [PATCH 27/32] add prompt for missing pubkeys --- .../thunderbird-element-replacer.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 9d6b3c8da86..499d22481b1 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -58,7 +58,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { verificationPubs, }); if (result.success && result.content) { - const decryptedContent = result.content.toUtfStr(); + const pgpBlockContent = result.content.toUtfStr(); const encryptionStatus = result.isEncrypted ? 'encrypted' : 'not encrypted'; let verificationStatus = ''; if (result?.signature) { @@ -70,7 +70,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { verificationStatus = 'not signed'; } } - const pgpBlock = this.generatePgpBlockTemplate(encryptionStatus, verificationStatus, decryptedContent); + const pgpBlock = this.generatePgpBlockTemplate(encryptionStatus, verificationStatus, pgpBlockContent); $('body').html(pgpBlock); // xss-sanitized } else { const decryptErr = result as DecryptError; @@ -98,15 +98,18 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { result = await MsgUtil.verifyDetached({ plaintext: detachedSignatureParams.plaintext, sigText: detachedSignatureParams.sigText, verificationPubs }); } let verificationStatus = ''; - let signedMessage = ''; - if (result.match && result.content) { - verificationStatus = 'signed'; - signedMessage = result.content.toUtfStr(); + let pgpBlockContent = ''; + if (result.content) { + verificationStatus = result.match ? 'signed' : 'not signed'; + if (result.signerLongids) { + verificationStatus = `could not verify signature: missing pubkey ${result.signerLongids}`; + } + pgpBlockContent = result.content.toUtfStr(); } else if (result.error) { verificationStatus = `could not verify signature: ${result.error}`; - signedMessage = detachedSignatureParams?.plaintext || ''; + pgpBlockContent = detachedSignatureParams?.plaintext || ''; } - const pgpBlock = this.generatePgpBlockTemplate('not encrypted', verificationStatus, signedMessage); + const pgpBlock = this.generatePgpBlockTemplate('not encrypted', verificationStatus, pgpBlockContent); $('body').html(pgpBlock); // xss-sanitized }; From f2ea24fa494796a1a6e7bf63eb018f2d329e6e34 Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 25 Oct 2024 12:46:30 +0800 Subject: [PATCH 28/32] wip --- extension/js/common/browser/browser-msg.ts | 2 +- .../thunderbird/thunderbird-element-replacer.ts | 16 ++++++++-------- extension/js/service_worker/bg-handlers.ts | 8 +++++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index 1c87942e5b8..738e2e5c9c7 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -116,7 +116,7 @@ export namespace Bm { export type ExpirationCacheGet = Promise; export type ExpirationCacheSet = Promise; export type ExpirationCacheDeleteExpired = Promise; - export type ThunderbirdGetDownloadableAttachment = ThunderbirdAttachment[]; + export type ThunderbirdGetDownloadableAttachment = { from: string; processableAttachments: ThunderbirdAttachment[] }; export type ThunderbirdGetCurrentUser = string | undefined; export type ThunderbirdOpenPassphraseDialog = Promise; export type ThunderbirdInitiateAttachmentDownload = Promise; diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 499d22481b1..fa48a23ffd8 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -29,21 +29,21 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; public handleThunderbirdMessageParsing = async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.acctEmail = (await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser())!; const emailBodyToParse = $('div.moz-text-plain').text().trim() || $('div.moz-text-html').text().trim(); + const { processableAttachments: fcAttachments, from: from } = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment(); if (Catch.isThunderbirdMail()) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.acctEmail = (await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser())!; - const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, this.acctEmail))?.sortedPubkeys ?? []; - const signerKeys = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); + const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, from))?.sortedPubkeys ?? []; + const verificationPubs = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); if (this.resemblesAsciiArmoredMsg(emailBodyToParse)) { - await this.messageDecrypt(signerKeys, this.emailBodyFromThunderbirdMail); + await this.messageDecrypt(verificationPubs, this.emailBodyFromThunderbirdMail); } else if (this.resemblesSignedMsg(emailBodyToParse)) { - await this.messageVerify(signerKeys); + await this.messageVerify(verificationPubs); } - const fcAttachments = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment(); if (fcAttachments.length) { for (const fcAttachment of fcAttachments) { - await this.attachmentUiRenderer(fcAttachment, signerKeys, emailBodyToParse); + await this.attachmentUiRenderer(fcAttachment, verificationPubs, emailBodyToParse); } } $('body').show(); diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index a185d06b703..f9bc41fa04b 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -13,7 +13,7 @@ import { GoogleOAuth } from '../common/api/authentication/google/google-oauth.js import { AcctStore } from '../common/platform/store/acct-store.js'; import { ConfiguredIdpOAuth } from '../common/api/authentication/configured-idp-oauth.js'; import { Url, Str } from '../common/core/common.js'; -import { Attachment } from '../common/core/attachment.js'; +import { Attachment, ThunderbirdAttachment } from '../common/core/attachment.js'; export class BgHandlers { public static openSettingsPageHandler: Bm.AsyncResponselessHandler = async ({ page, path, pageUrlParams, addNewAcct, acctEmail }: Bm.Settings) => { @@ -164,10 +164,12 @@ export class BgHandlers { }; public static thunderbirdGetDownloadableAttachment = async (): Promise => { - const processableAttachments: Bm.Res.ThunderbirdGetDownloadableAttachment = []; + const processableAttachments: ThunderbirdAttachment[] = []; const [tab] = await messenger.mailTabs.query({ active: true, currentWindow: true }); const message = await messenger.messageDisplay.getDisplayedMessage(tab.id); + let from = ''; if (tab.id && message?.id) { + from = Str.parseEmail(message.author).email || ''; const attachments = await messenger.messages.listAttachments(message.id); const fcAttachments: Attachment[] = []; // convert Thunderbird Attachments to FlowCrypt recognizable Attachments @@ -191,7 +193,7 @@ export class BgHandlers { }); } } - return processableAttachments; + return { from, processableAttachments }; }; public static thunderbirdInitiateAttachmentDownload = async ( From 7969a2ca2e65e1f1eed30d27cf99f6c84d18c6b8 Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 25 Oct 2024 16:45:28 +0800 Subject: [PATCH 29/32] added detached signature verification? --- .../thunderbird/thunderbird-element-replacer.ts | 13 +++++++------ extension/js/service_worker/bg-handlers.ts | 6 +++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index fa48a23ffd8..6255965ea49 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -31,10 +31,10 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { public handleThunderbirdMessageParsing = async () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.acctEmail = (await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser())!; - const emailBodyToParse = $('div.moz-text-plain').text().trim() || $('div.moz-text-html').text().trim(); - const { processableAttachments: fcAttachments, from: from } = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment(); + const emailBodyToParse = $('div.moz-text-plain').text().trim() || $('div.moz-text-html').text().trim() || $('div.moz-text-flowed').text().trim(); + const { processableAttachments: fcAttachments, from: signerEmail } = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment(); if (Catch.isThunderbirdMail()) { - const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, from))?.sortedPubkeys ?? []; + const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, signerEmail))?.sortedPubkeys ?? []; const verificationPubs = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); if (this.resemblesAsciiArmoredMsg(emailBodyToParse)) { await this.messageDecrypt(verificationPubs, this.emailBodyFromThunderbirdMail); @@ -101,13 +101,13 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { let pgpBlockContent = ''; if (result.content) { verificationStatus = result.match ? 'signed' : 'not signed'; - if (result.signerLongids) { + if (!result.signerLongids.length) { verificationStatus = `could not verify signature: missing pubkey ${result.signerLongids}`; } pgpBlockContent = result.content.toUtfStr(); } else if (result.error) { verificationStatus = `could not verify signature: ${result.error}`; - pgpBlockContent = detachedSignatureParams?.plaintext || ''; + pgpBlockContent = detachedSignatureParams?.plaintext || this.emailBodyFromThunderbirdMail; } const pgpBlock = this.generatePgpBlockTemplate('not encrypted', verificationStatus, pgpBlockContent); $('body').html(pgpBlock); // xss-sanitized @@ -122,10 +122,11 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { !this.emailBodyFromThunderbirdMail ) { await this.messageDecrypt(verificationPubs, fcAttachment.data); + // detached signature verification } else if (fcAttachment.treatAs === 'signature') { const sigText = new TextDecoder('utf-8').decode(fcAttachment.data).trim(); if (this.resemblesSignedMsg(sigText)) { - await this.messageVerify(verificationPubs, { plaintext: emailBodyToParse, sigText }); + await this.messageVerify(verificationPubs, { plaintext: emailBodyToParse, sigText: sigText.replace('\n=3D', '\n=') }); } } }; diff --git a/extension/js/service_worker/bg-handlers.ts b/extension/js/service_worker/bg-handlers.ts index f9bc41fa04b..8a57f1af531 100644 --- a/extension/js/service_worker/bg-handlers.ts +++ b/extension/js/service_worker/bg-handlers.ts @@ -170,8 +170,12 @@ export class BgHandlers { let from = ''; if (tab.id && message?.id) { from = Str.parseEmail(message.author).email || ''; - const attachments = await messenger.messages.listAttachments(message.id); + const mimeMsg = await messenger.messages.getFull(message.id); + let attachments = await messenger.messages.listAttachments(message.id); const fcAttachments: Attachment[] = []; + if (mimeMsg.parts?.[0].contentType === 'multipart/signed' && mimeMsg.parts?.[0].parts?.length === 2) { + attachments = attachments.filter(file => file.contentType === 'application/pgp-signature'); + } // convert Thunderbird Attachments to FlowCrypt recognizable Attachments for (const attachment of attachments) { const file = await messenger.messages.getAttachmentFile(message.id, attachment.partName); From 55d7e6268e3c40b12459e19b33beec6620fd23bd Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 15 Nov 2024 17:26:51 +0800 Subject: [PATCH 30/32] pr review: cleanup --- .../thunderbird-element-replacer.ts | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 6255965ea49..95c063aa6ce 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -9,7 +9,6 @@ import { KeyUtil } from '../../../common/core/crypto/key.js'; import { DecryptError, DecryptErrTypes, MsgUtil, VerifyRes } from '../../../common/core/crypto/pgp/msg-util.js'; import { OpenPGPKey } from '../../../common/core/crypto/pgp/openpgp-key.js'; import { PgpArmor } from '../../../common/core/crypto/pgp/pgp-armor'; -import { Catch } from '../../../common/platform/catch'; import { ContactStore } from '../../../common/platform/store/contact-store.js'; import { KeyStore } from '../../../common/platform/store/key-store.js'; import { Xss } from '../../../common/platform/xss.js'; @@ -33,21 +32,19 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { this.acctEmail = (await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser())!; const emailBodyToParse = $('div.moz-text-plain').text().trim() || $('div.moz-text-html').text().trim() || $('div.moz-text-flowed').text().trim(); const { processableAttachments: fcAttachments, from: signerEmail } = await BrowserMsg.send.bg.await.thunderbirdGetDownloadableAttachment(); - if (Catch.isThunderbirdMail()) { - const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, signerEmail))?.sortedPubkeys ?? []; - const verificationPubs = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); - if (this.resemblesAsciiArmoredMsg(emailBodyToParse)) { - await this.messageDecrypt(verificationPubs, this.emailBodyFromThunderbirdMail); - } else if (this.resemblesSignedMsg(emailBodyToParse)) { - await this.messageVerify(verificationPubs); - } - if (fcAttachments.length) { - for (const fcAttachment of fcAttachments) { - await this.attachmentUiRenderer(fcAttachment, verificationPubs, emailBodyToParse); - } + const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, signerEmail))?.sortedPubkeys ?? []; + const verificationPubs = parsedPubs.map(key => KeyUtil.armor(key.pubkey)); + if (this.resemblesAsciiArmoredMsg(emailBodyToParse)) { + await this.messageDecrypt(verificationPubs, this.emailBodyFromThunderbirdMail); + } else if (this.resemblesSignedMsg(emailBodyToParse)) { + await this.messageVerify(verificationPubs); + } + if (fcAttachments.length) { + for (const fcAttachment of fcAttachments) { + await this.attachmentUiRenderer(fcAttachment, verificationPubs, emailBodyToParse); } - $('body').show(); } + $('body').show(); }; private messageDecrypt = async (verificationPubs: string[], encryptedData: string | Buf) => { From 5ef5e8bcc6a6e16040bc76ee93223f42ea162773 Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 15 Nov 2024 17:29:36 +0800 Subject: [PATCH 31/32] pr review: code cleanup --- .../webmail/thunderbird/thunderbird-element-replacer.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 95c063aa6ce..35ad57b95ac 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -149,11 +149,7 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { const decryptedFileName = originalFilename.replace(/\.(pgp|gpg|asc)$/i, ''); const uiFileExtensions = ['excel', 'word', 'png', 'jpg']; const matchedExtension = uiFileExtensions.find(fileExtension => decryptedFileName.endsWith(fileExtension)); - if (matchedExtension) { - attachmentFileTypeIcon.attr('src', messenger.runtime.getURL(`/img/fileformat/${matchedExtension}.png`)); - } else { - attachmentFileTypeIcon.attr('src', messenger.runtime.getURL(`/img/fileformat/generic.png`)); - } + attachmentFileTypeIcon.attr('src', messenger.runtime.getURL(`/img/fileformat/${matchedExtension || 'generic'}.png`)); const attachmentFilename = $('
').addClass('thunderbird_attachment_name').text(originalFilename); const attachmentDownloadBtn = $('
') .addClass('thunderbird_attachment_download') From 8a36e6cee16b9674bb05a0096c207b14503b5afe Mon Sep 17 00:00:00 2001 From: martgil Date: Fri, 15 Nov 2024 17:52:26 +0800 Subject: [PATCH 32/32] pr review: remove check for non-optional object --- .../thunderbird-element-replacer.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts index 35ad57b95ac..7c003feebf0 100644 --- a/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts +++ b/extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts @@ -164,17 +164,15 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer { }; private downloadThunderbirdAttachmentHandler = async (decryptedFileName: string, encryptedData: Buf) => { - if (encryptedData) { - const result = await MsgUtil.decryptMessage({ - kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), - encryptedData, - verificationPubs: [], // todo: #4158 signature verification of attachments - }); - if (result.success && result.content) { - await BrowserMsg.send.bg.await.thunderbirdInitiateAttachmentDownload({ decryptedFileName, decryptedContent: result.content }); - } - // no need to handle DecryptErrTypes.needPassphrase it was already handled by this.messageDecrypt() + const result = await MsgUtil.decryptMessage({ + kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), + encryptedData, + verificationPubs: [], // todo: #4158 signature verification of attachments + }); + if (result.success && result.content) { + await BrowserMsg.send.bg.await.thunderbirdInitiateAttachmentDownload({ decryptedFileName, decryptedContent: result.content }); } + // no need to handle DecryptErrTypes.needPassphrase it was already handled by this.messageDecrypt() }; private resemblesSignedMsg = (body: string) => {