Skip to content

Commit 2cd3e47

Browse files
committed
Add signature verification in Thunderbird (cleartext message)
1 parent 20a8b03 commit 2cd3e47

File tree

1 file changed

+36
-19
lines changed

1 file changed

+36
-19
lines changed

extension/js/content_scripts/webmail/thunderbird/thunderbird-element-replacer.ts

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import { BrowserMsg } from '../../../common/browser/browser-msg.js';
66
import { KeyUtil } from '../../../common/core/crypto/key.js';
77
import { DecryptError, DecryptErrTypes, DecryptSuccess, MsgUtil } from '../../../common/core/crypto/pgp/msg-util.js';
8+
import { OpenPGPKey } from '../../../common/core/crypto/pgp/openpgp-key.js';
89
import { PgpArmor } from '../../../common/core/crypto/pgp/pgp-armor';
910
import { Catch } from '../../../common/platform/catch';
1011
import { ContactStore } from '../../../common/platform/store/contact-store.js';
1112
import { KeyStore } from '../../../common/platform/store/key-store.js';
1213
import { IntervalFunction, WebmailElementReplacer } from '../generic/webmail-element-replacer.js';
14+
import * as openpgp from 'openpgp';
1315

1416
export class ThunderbirdElementReplacer extends WebmailElementReplacer {
1517
public setReplyBoxEditable: () => Promise<void>;
@@ -44,41 +46,56 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer {
4446
}
4547
if (result.content) {
4648
const decryptedMsg = result.content.toUtfStr();
47-
const verificationStatus = (result as DecryptSuccess).signature?.match; // todo: signature verification could result to error, show verification error in badge
48-
const encryptedStatus = (result as DecryptSuccess).isEncrypted;
49-
const pgpBlockTemplate = `
49+
const verificationStatus = (result as DecryptSuccess).signature?.match ? 'signed' : 'not signed'; // todo: signature verification could result to error, show verification error in badge
50+
const encryptionStatus = (result as DecryptSuccess).isEncrypted ? 'encrypted' : 'not encrypted';
51+
const pgpBlock = this.generatePgpBlockTemplate(encryptionStatus, verificationStatus, decryptedMsg);
52+
$('body').html(pgpBlock); // xss-sanitized
53+
}
54+
} else if (fullMsg.parts?.[0].parts && this.isCleartextMsg(fullMsg)) {
55+
const acctEmail = await BrowserMsg.send.bg.await.thunderbirdGetCurrentUser();
56+
const parsedPubs = (await ContactStore.getOneWithAllPubkeys(undefined, String(acctEmail)))?.sortedPubkeys ?? [];
57+
const signerKeys = parsedPubs.map(key => KeyUtil.armor(key.pubkey));
58+
const cleartextMessage = String(fullMsg.parts[0].parts[0].body?.trim());
59+
const message = await openpgp.readCleartextMessage({ cleartextMessage });
60+
const result = await OpenPGPKey.verify(message, await ContactStore.getPubkeyInfos(undefined, signerKeys));
61+
if (result.match) {
62+
const pgpBlock = this.generatePgpBlockTemplate('not encrypted', 'signed', result?.content?.toUtfStr() || '');
63+
$('body').html(pgpBlock); // xss-sanitized
64+
}
65+
// add fallback rendering for invalid/bad signatures
66+
}
67+
// else if detached signed message
68+
}
69+
}
70+
};
71+
72+
private generatePgpBlockTemplate = (encryptionStatus: string, verificationStatus: string, messageToRender: string): string => {
73+
const pgpBlockTemplate = `
5074
<div class="pgp_secure">
5175
<div>
52-
<div id="pgp_encryption" class="pgp_badge short ${encryptedStatus ? 'green_label' : 'red_label'}">${encryptedStatus ? 'encrypted' : 'not encrypted'}</div>
53-
<div id="pgp_signature" class="pgp_badge short ${verificationStatus ? 'green_label' : 'red_label'}">${verificationStatus ? 'signed' : 'not signed'}</div>
76+
<div id="pgp_encryption" class="pgp_badge short ${encryptionStatus === 'encrypted' ? 'green_label' : 'red_label'}">${encryptionStatus}</div>
77+
<div id="pgp_signature" class="pgp_badge short ${verificationStatus === 'signed' ? 'green_label' : 'red_label'}">${verificationStatus}</div>
5478
</div>
5579
<div class="pgp_block">
56-
<pre>${decryptedMsg}</pre>
80+
<pre>${messageToRender}</pre>
5781
</div>
5882
</div>`;
59-
$('body').html(pgpBlockTemplate); // xss-sanitized
60-
}
61-
} else if (this.isCleartextSignedMsg(fullMsg)) {
62-
console.log('perform cleartext signed message verification!');
63-
}
64-
// else if signed message found
65-
}
66-
}
83+
return pgpBlockTemplate;
6784
};
6885

69-
private isCleartextSignedMsg = (fullMsg: messenger.messages.MessagePart): boolean => {
70-
const isClearTextSignedMsg =
86+
private isCleartextMsg = (fullMsg: messenger.messages.MessagePart): boolean => {
87+
const isClearTextMsg =
7188
(fullMsg.headers &&
7289
'openpgp' in fullMsg.headers &&
7390
fullMsg.parts &&
7491
fullMsg.parts[0]?.parts?.length === 1 &&
7592
fullMsg.parts[0].parts[0].contentType === 'text/plain' &&
76-
this.resemblesCleartextSignedMsg(fullMsg.parts[0].parts[0].body?.trim() || '')) ||
93+
this.resemblesCleartextMsg(fullMsg.parts[0].parts[0].body?.trim() || '')) ||
7794
false;
78-
return isClearTextSignedMsg;
95+
return isClearTextMsg;
7996
};
8097

81-
private resemblesCleartextSignedMsg = (body: string) => {
98+
private resemblesCleartextMsg = (body: string) => {
8299
return (
83100
body.startsWith(PgpArmor.ARMOR_HEADER_DICT.signedMsg.begin) &&
84101
body.includes(String(PgpArmor.ARMOR_HEADER_DICT.signedMsg.middle)) &&

0 commit comments

Comments
 (0)