|
5 | 5 | import { BrowserMsg } from '../../../common/browser/browser-msg.js';
|
6 | 6 | import { KeyUtil } from '../../../common/core/crypto/key.js';
|
7 | 7 | import { DecryptError, DecryptErrTypes, DecryptSuccess, MsgUtil } from '../../../common/core/crypto/pgp/msg-util.js';
|
| 8 | +import { OpenPGPKey } from '../../../common/core/crypto/pgp/openpgp-key.js'; |
8 | 9 | import { PgpArmor } from '../../../common/core/crypto/pgp/pgp-armor';
|
9 | 10 | import { Catch } from '../../../common/platform/catch';
|
10 | 11 | import { ContactStore } from '../../../common/platform/store/contact-store.js';
|
11 | 12 | import { KeyStore } from '../../../common/platform/store/key-store.js';
|
12 | 13 | import { IntervalFunction, WebmailElementReplacer } from '../generic/webmail-element-replacer.js';
|
| 14 | +import * as openpgp from 'openpgp'; |
13 | 15 |
|
14 | 16 | export class ThunderbirdElementReplacer extends WebmailElementReplacer {
|
15 | 17 | public setReplyBoxEditable: () => Promise<void>;
|
@@ -44,41 +46,56 @@ export class ThunderbirdElementReplacer extends WebmailElementReplacer {
|
44 | 46 | }
|
45 | 47 | if (result.content) {
|
46 | 48 | 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 = ` |
50 | 74 | <div class="pgp_secure">
|
51 | 75 | <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> |
54 | 78 | </div>
|
55 | 79 | <div class="pgp_block">
|
56 |
| - <pre>${decryptedMsg}</pre> |
| 80 | + <pre>${messageToRender}</pre> |
57 | 81 | </div>
|
58 | 82 | </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; |
67 | 84 | };
|
68 | 85 |
|
69 |
| - private isCleartextSignedMsg = (fullMsg: messenger.messages.MessagePart): boolean => { |
70 |
| - const isClearTextSignedMsg = |
| 86 | + private isCleartextMsg = (fullMsg: messenger.messages.MessagePart): boolean => { |
| 87 | + const isClearTextMsg = |
71 | 88 | (fullMsg.headers &&
|
72 | 89 | 'openpgp' in fullMsg.headers &&
|
73 | 90 | fullMsg.parts &&
|
74 | 91 | fullMsg.parts[0]?.parts?.length === 1 &&
|
75 | 92 | 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() || '')) || |
77 | 94 | false;
|
78 |
| - return isClearTextSignedMsg; |
| 95 | + return isClearTextMsg; |
79 | 96 | };
|
80 | 97 |
|
81 |
| - private resemblesCleartextSignedMsg = (body: string) => { |
| 98 | + private resemblesCleartextMsg = (body: string) => { |
82 | 99 | return (
|
83 | 100 | body.startsWith(PgpArmor.ARMOR_HEADER_DICT.signedMsg.begin) &&
|
84 | 101 | body.includes(String(PgpArmor.ARMOR_HEADER_DICT.signedMsg.middle)) &&
|
|
0 commit comments