Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
08cc689
wip: refactor
martgil Jul 24, 2024
dd6b230
wip: Register content script for Thunderbird
martgil Jul 24, 2024
87dea43
refactor: move Thunderbird event handler and content script registrat…
martgil Jul 24, 2024
951f944
wip: update manifest.json
martgil Jul 25, 2024
24e73d0
wip
martgil Jul 26, 2024
eb5a1b0
wip
martgil Jul 28, 2024
cd8dbb9
wip
martgil Jul 28, 2024
c4467b6
wip
martgil Jul 28, 2024
79c33b7
wip
martgil Jul 31, 2024
8ad4a5e
Merge remote-tracking branch 'origin/master' into issue-5667-added-me…
martgil Jul 31, 2024
183dfed
wip
martgil Jul 31, 2024
9e061d4
added functionality to obtain pgp message from a background script
martgil Jul 31, 2024
8cbe0ea
fix openpgp undefined in content-script
martgil Aug 1, 2024
e015143
wip
martgil Aug 1, 2024
3462ce6
Merge remote-tracking branch 'origin/master' into issue-5667-added-me…
martgil Aug 5, 2024
8b44889
Merge branch 'master' into issue-5667-added-message-decryption-on-thu…
martgil Aug 15, 2024
518d38b
Add "thunderbird_get_current_user" to BrowserMsg
martgil Aug 16, 2024
f3f99ab
Add "thunderbird_msg_decrypt" to BrowserMsg
martgil Aug 16, 2024
1472fd5
fix BrowserMsg listener names
martgil Aug 16, 2024
92b3263
Merge remote-tracking branch 'origin/master' into issue-5667-added-me…
martgil Aug 20, 2024
9afaf6e
wip
martgil Aug 21, 2024
abf898b
wip
martgil Aug 21, 2024
f901bc5
fix missing opgp reference in Thunderbird port
martgil Aug 21, 2024
ab34c37
Merge branch 'master' into issue-5667-added-message-decryption-on-thu…
martgil Aug 23, 2024
b719396
Added pgp message decryption
martgil Aug 24, 2024
fd54d62
fix failing test
martgil Aug 24, 2024
c19f7f4
run element replacer in heartbeat (runIntervalFunctionsPeriodically)
martgil Aug 24, 2024
8cf725f
Improved decrypted message rendering in Thunderbird
martgil Aug 24, 2024
25df0e6
Add cleartext signed message detection
martgil Aug 24, 2024
20a8b03
wip
martgil Aug 25, 2024
2cd3e47
Add signature verification in Thunderbird (cleartext message)
martgil Aug 25, 2024
7565afb
wip
martgil Aug 25, 2024
f89e496
wip: Better signature verification handling
martgil Aug 25, 2024
ba0ed46
wip
martgil Aug 25, 2024
cc7aba4
wip
martgil Aug 25, 2024
0dddb00
cleanup
martgil Aug 25, 2024
cf24357
Merge branch 'master' into issue-5667-added-message-decryption-on-thu…
martgil Aug 26, 2024
adafaa9
Add passphrase dialog prompt in Thunderbird
martgil Aug 27, 2024
8fba4f2
Add autoclose for completely unlock passphrase in Thunderbird
martgil Aug 27, 2024
3203a2f
add another case for encrypted message detection
martgil Aug 27, 2024
53589ef
fix unsafe assignment of an any value
martgil Aug 28, 2024
50ea4aa
better content script bundling
martgil Aug 28, 2024
d78693c
cleanup
martgil Aug 28, 2024
909684c
Add todo in preparation for post thunderbird development
martgil Aug 28, 2024
cb7765f
Merge branch 'master' into issue-5667-added-message-decryption-on-thu…
martgil Sep 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 1 addition & 37 deletions extension/js/common/browser/browser-msg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { AuthRes } from '../api/authentication/google/google-oauth.js';
import { AjaxErr } from '../api/shared/api-error.js';
import { Buf } from '../core/buf.js';
import { Dict, Str, Url, UrlParams } from '../core/common.js';
import { Dict, Str, UrlParams } from '../core/common.js';
import { NotificationGroupType } from '../notifications.js';
import { Catch } from '../platform/catch.js';
import { PassphraseDialogType } from '../xss-safe-factory.js';
Expand All @@ -18,8 +18,6 @@ import { RenderMessage } from '../render-message.js';
import { SymEncryptedMessage, SymmetricMessageEncryption } from '../symmetric-message-encryption.js';
import { Ajax as ApiAjax, ResFmt } from '../api/shared/api.js';
import { Ui } from './ui.js';
import { GlobalStore } from '../platform/store/global-store.js';
import { BgUtils } from '../../service_worker/bgutils.js';

export type GoogleAuthWindowResult$result = 'Success' | 'Denied' | 'Error' | 'Closed';
export type ScreenDimensions = { width: number; height: number; availLeft: number; availTop: number };
Expand Down Expand Up @@ -453,40 +451,6 @@ export class BrowserMsg {
});
}

public static thunderbirdSecureComposeHandler() {
const handleClickEvent = async (tabId: number, acctEmail: string, thunderbirdMsgId: number, composeMethod?: messenger.compose._ComposeDetailsType) => {
const accountEmails = await GlobalStore.acctEmailsGet();
const useFullScreenSecureCompose = (await messenger.windows.getCurrent()).type === 'messageCompose';
composeMethod = composeMethod === 'reply' || composeMethod === 'forward' ? composeMethod : undefined;
if (accountEmails.length !== 0) {
await BgUtils.openExtensionTab(
Url.create('/chrome/settings/inbox/inbox.htm', { acctEmail, useFullScreenSecureCompose, thunderbirdMsgId, composeMethod })
);
await messenger.tabs.remove(tabId);
} else {
await BgUtils.openExtensionTab(Url.create('/chrome/settings/initial.htm', {}));
}
};
messenger.composeAction.onClicked.addListener(async tab => {
const messageDetails = await messenger.compose.getComposeDetails(Number(tab.id));
const composeMethod = messageDetails.type;
const msgId = Number(messageDetails.relatedMessageId);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const acctEmail = Str.parseEmail(messageDetails.from as string).email!;
await handleClickEvent(Number(tab.id), acctEmail, msgId, composeMethod);
});
messenger.messageDisplayAction.onClicked.addListener(async tab => {
const tabId = Number(tab.id);
const messageDetails = await messenger.messageDisplay.getDisplayedMessage(tabId);
if (messageDetails) {
const msgId = messageDetails.id;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const acctEmail = (await messenger.accounts.get(messageDetails.folder!.accountId)).name;
await handleClickEvent(tabId, acctEmail, msgId);
}
});
}

public static alarmListen() {
const alarmListener = (alarm: { name: string }) => {
const alarmName = alarm.name;
Expand Down
2 changes: 1 addition & 1 deletion extension/js/common/browser/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class Env {

// eslint-disable-next-line @typescript-eslint/require-await
public static async webmails(): Promise<WebMailName[]> {
return ['gmail']; // async because storage may be involved in the future
return ['gmail', 'thunderbird']; // async because storage may be involved in the future
}

public static getBaseUrl() {
Expand Down
8 changes: 5 additions & 3 deletions extension/js/common/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ export class Injector {
}

public meta = () => {
this.S.cached('body')
.addClass(`cryptup_${this.webmailName} cryptup_${this.webmailName}_${this.webmailVariant} ${Catch.browser().name}`)
.append(this.factory.metaStylesheet('webmail') + this.factory.metaNotificationContainer()); // xss-safe-factory
if (!Catch.isThunderbirdMail()) {
this.S.cached('body')
.addClass(`cryptup_${this.webmailName} cryptup_${this.webmailName}_${this.webmailVariant} ${Catch.browser().name}`)
.append(this.factory.metaStylesheet('webmail') + this.factory.metaNotificationContainer()); // xss-safe-factory
}
};

public openComposeWin = (draftId?: string, openInFullScreen?: boolean, thunderbirdMsgId?: number, replyOption?: string, replyMsgId?: string): boolean => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
let acctEmailInterval = 1000;
const webmails = await Env.webmails();
while (true) {
const acctEmail = webmailSpecific.getUserAccountEmail();
// todo - find a way to obtain current user account from Thundermail
let acctEmail: string | undefined;
if (Catch.isThunderbirdMail()) {
acctEmail = (await messenger.runtime.sendMessage('thunderbird_get_current_user')) as string; // todo - add to BrowserMsg
} else {
acctEmail = webmailSpecific.getUserAccountEmail();
}
if (typeof acctEmail !== 'undefined') {
win.account_email_global = acctEmail;
if (webmails.includes(webmailSpecific.name)) {
Expand Down Expand Up @@ -433,12 +439,16 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
}
const acctEmail = await waitForAcctEmail();
const { tabId, notifications, factory, inject } = await initInternalVars(acctEmail);
await showNotificationsAndWaitTilAcctSetUp(acctEmail, notifications);

Catch.setHandledTimeout(() => updateClientConfiguration(acctEmail), 0);
const ppEvent: { entered?: boolean } = {};
const relayManager = new RelayManager();
browserMsgListen(acctEmail, tabId, inject, factory, notifications, relayManager, ppEvent);
if (webmailSpecific.name === 'gmail') {
await showNotificationsAndWaitTilAcctSetUp(acctEmail, notifications);
browserMsgListen(acctEmail, tabId, inject, factory, notifications, relayManager, ppEvent);
}
const clientConfiguration = await ClientConfiguration.newInstance(acctEmail);
// todo - investigate startPullingKeysFromEkm in Thunderbird
await startPullingKeysFromEkm(
acctEmail,
clientConfiguration,
Expand Down Expand Up @@ -515,6 +525,9 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
document.dispatchEvent(new CustomEvent(win.destruction_event));
document.addEventListener(win.destruction_event, win.destroy);

if (Catch.isThunderbirdMail()) {
await entrypoint();
}
if (win.vacant()) {
await entrypoint();
} else if (Catch.isFirefox()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,28 @@

'use strict';

import { Catch } from '../../../common/platform/catch';
import { IntervalFunction, WebmailElementReplacer } from '../generic/webmail-element-replacer';

export class ThunderbirdElementReplacer extends WebmailElementReplacer {
public getIntervalFunctions: () => IntervalFunction[];
// public getIntervalFunctions: () => IntervalFunction[];
public setReplyBoxEditable: () => Promise<void>;
public reinsertReplyBox: (replyMsgId: string) => void;
public scrollToReplyBox: (replyMsgId: string) => void;
public scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void;

public getIntervalFunctions = (): IntervalFunction[] => {
return [{ interval: 1000, handler: () => this.replaceThunderbirdMsgPane() }];
};

private replaceThunderbirdMsgPane = () => {
if (Catch.isThunderbirdMail()) {
console.log('todo');
// const fullMsg = (await messenger.runtime.sendMessage('decrypt')) as messenger.messages.MessagePart;
// if (fullMsg?.headers && 'openpgp' in fullMsg.headers) {
// // note : embeddedMsg for pgp_block injection -> replaceArmoredBlocks
// // do secure compose badge injection eg. signed or encrypted, (secure email status rendering) etc
// // render decrypted message right into the messageDisplay
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@ import { ClientConfiguration } from '../../../common/client-configuration';
import { Injector } from '../../../common/inject';
import { Notifications } from '../../../common/notifications';
import { contentScriptSetupIfVacant } from '../generic/setup-webmail-content-script';
import { GmailElementReplacer } from '../gmail/gmail-element-replacer';
import { ThunderbirdElementReplacer } from './thunderbird-element-replacer';

export class ThunderbirdWebmailStartup {
private replacer: GmailElementReplacer;
private replacer: ThunderbirdElementReplacer;

public asyncConstructor = async () => {
await contentScriptSetupIfVacant({
name: 'thunderbird',
variant: undefined,
getUserAccountEmail: () => undefined, // todo, but can start with undefined
getUserFullName: () => undefined, // todo, but can start with undefined
getReplacer: () => new ThunderbirdElementReplacer(), // todo - add this class empty, methods do nothing
getReplacer: () => this.replacer, // todo - add this class empty, methods do nothing
start: this.start,
});
};
Expand All @@ -29,6 +28,7 @@ export class ThunderbirdWebmailStartup {
// relayManager: RelayManager // todo in another issue
) => {
// injector.btns(); // todo in another issue - add compose button
this.replacer = new ThunderbirdElementReplacer();
this.replacer.runIntervalFunctionsPeriodically();
await notifications.showInitial(acctEmail);
notifications.show(
Expand Down
18 changes: 16 additions & 2 deletions extension/js/service_worker/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,22 @@ console.info('background.js service worker starting');
await BgHandlers.updateUninstallUrl({});
injectFcIntoWebmail();

// Thunderbird event handlers
if (Catch.isThunderbirdMail()) {
BrowserMsg.thunderbirdSecureComposeHandler();
BgHandlers.thunderbirdSecureComposeHandler();
await BgHandlers.thunderbirdContentScriptRegistration();
// todo - add background listener here with name "thunderbird_msg_decrypt"
// todo - move this to BrowserMsg
messenger.runtime.onMessage.addListener(async (message, sender) => {
if (message === 'thunderbird_get_current_user') {
if (sender.tab?.id) {
const messageDetails = await messenger.messageDisplay.getDisplayedMessage(sender.tab?.id);
if (messageDetails) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return (await messenger.accounts.get(messageDetails.folder!.accountId)).name;
}
}
}
return;
});
}
})().catch(Catch.reportErr);
42 changes: 42 additions & 0 deletions extension/js/service_worker/bg-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ContactStore } from '../common/platform/store/contact-store.js';
import { Api } from '../common/api/shared/api.js';
import { ExpirationCache } from '../common/core/expiration-cache.js';
import { GoogleOAuth } from '../common/api/authentication/google/google-oauth.js';
import { Url, Str } from '../common/core/common.js';

export class BgHandlers {
public static openSettingsPageHandler: Bm.AsyncResponselessHandler = async ({ page, path, pageUrlParams, addNewAcct, acctEmail }: Bm.Settings) => {
Expand Down Expand Up @@ -106,4 +107,45 @@ export class BgHandlers {
}
});
});

public static thunderbirdSecureComposeHandler = () => {
const handleClickEvent = async (tabId: number, acctEmail: string, thunderbirdMsgId: number, composeMethod?: messenger.compose._ComposeDetailsType) => {
const accountEmails = await GlobalStore.acctEmailsGet();
const useFullScreenSecureCompose = (await messenger.windows.getCurrent()).type === 'messageCompose';
composeMethod = composeMethod === 'reply' || composeMethod === 'forward' ? composeMethod : undefined;
if (accountEmails.length !== 0) {
await BgUtils.openExtensionTab(
Url.create('/chrome/settings/inbox/inbox.htm', { acctEmail, useFullScreenSecureCompose, thunderbirdMsgId, composeMethod })
);
await messenger.tabs.remove(tabId);
} else {
await BgUtils.openExtensionTab(Url.create('/chrome/settings/initial.htm', {}));
}
};
messenger.composeAction.onClicked.addListener(async tab => {
const messageDetails = await messenger.compose.getComposeDetails(Number(tab.id));
const composeMethod = messageDetails.type;
const msgId = Number(messageDetails.relatedMessageId);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const acctEmail = Str.parseEmail(messageDetails.from as string).email!;
await handleClickEvent(Number(tab.id), acctEmail, msgId, composeMethod);
});
messenger.messageDisplayAction.onClicked.addListener(async tab => {
const tabId = Number(tab.id);
const messageDetails = await messenger.messageDisplay.getDisplayedMessage(tabId);
if (messageDetails) {
const msgId = messageDetails.id;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const acctEmail = (await messenger.accounts.get(messageDetails.folder!.accountId)).name;
await handleClickEvent(tabId, acctEmail, msgId);
}
});
};

public static thunderbirdContentScriptRegistration = async () => {
await messenger.messageDisplayScripts.register({
js: [{ file: './js/content_scripts/thunderbird-content-script.js' }],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after build changes all extension versions (not only thunderbird), include additional thunderbird-content-script.js which is almost 5mb:

Screenshot 2024-08-26 at 20 16 37

for browsers we use injectFcIntoWebmail method which adds all needed files to each gmail tab.

do you think it'll be possible to adapt this method for thunderbird?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after build changes all extension versions (not only thunderbird), include additional thunderbird-content-script.js which is almost 5mb:

Ah, yes - sorry about that. I'll find a way a viable alternative solution to it from which thunderbird-content-script.js should only be created for the Thunderbird build.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's start with trying to adapt injectFcIntoWebmail method to inject all needed scripts into thunderbird, so it'll work the same in browsers and thunderbird.
and if it doesn't work - then use thunderbird-content-script.js

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @sosnovsky - I tried to adapt using injectFcIntoWebmail and there are incompatibility that prevents me from somehow make it work as close as how it works on browser. For example, in Thunderbird, registration for content script is done within messageDisplayScript.register, from which it only accept one of the two parameter (file or code). And then several files doesnt seem to work when defined in .register() - https://webextension-api.thunderbird.net/en/91/messageDisplayScripts.html.

However, I have done my best to achieve the best result while still complying with the requirements, which I believe I have accomplished in this PR. Please review and test it out. From my perspective, the results are extremely positive, and I'm excited to hear your feedback. :)

Thanks for your help.

css: [{ file: './css/cryptup.css' }],
});
};
}
2 changes: 1 addition & 1 deletion tooling/build-types-and-manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ addManifest(
(manifest.browser_action as messenger._manifest.ActionManifest).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', 'accountsRead'];
manifest.permissions = [...(manifestV3.permissions ?? []), 'compose', '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
Expand Down
24 changes: 24 additions & 0 deletions tooling/bundle-content-scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,27 @@ buildContentScript(
),
'webmail_bundle.js'
);

// thunderbird
buildContentScript(
([] as string[]).concat(
getFilesInDir(`${sourceDir}/js/common/platform`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/platform/store`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/core`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/core/crypto/`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/core/crypto/smime`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/api/shared`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/api/key-server`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/api/account-servers`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/api/authentication/generic`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/api/authentication/google`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/api/authentication`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/api/email-provider`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/api/email-provider/gmail`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/api`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common/browser`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/common`, /\.js$/, false),
getFilesInDir(`${sourceDir}/js/content_scripts/webmail`, /\.js$/)
),
'thunderbird-content-script.js'
);