From b9ab3569ed3bff5895818251195f1214f428ca45 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Wed, 1 Mar 2023 11:18:57 +0000 Subject: [PATCH 1/5] #3448 Ignore signature for pubkey in protonmail --- extension/chrome/elements/attachment.ts | 3 +- .../compose-modules/compose-render-module.ts | 2 +- .../pgp-block-render-module.ts | 2 +- .../common/api/email-provider/gmail/gmail.ts | 2 +- extension/js/common/core/attachment.ts | 15 +- extension/js/common/core/common.ts | 5 + extension/js/common/core/mime.ts | 10 +- extension/js/common/core/msg-block-parser.ts | 2 +- .../webmail/gmail-element-replacer.ts | 2 +- .../message-export-1869220e0c8f16dd.json | 133 ++++++++++++++++++ test/source/mock/google/google-data.ts | 30 +++- test/source/tests/decrypt.ts | 85 +++++++++++ 12 files changed, 276 insertions(+), 15 deletions(-) create mode 100644 test/source/mock/google/exported-messages/message-export-1869220e0c8f16dd.json diff --git a/extension/chrome/elements/attachment.ts b/extension/chrome/elements/attachment.ts index aa4ffd53993..971b3df6c8c 100644 --- a/extension/chrome/elements/attachment.ts +++ b/extension/chrome/elements/attachment.ts @@ -238,7 +238,8 @@ export class AttachmentDownloadView extends View { }; private processAsPublicKeyAndHideAttachmentIfAppropriate = async () => { - if (this.attachment.msgId && this.attachment.id && this.attachment.treatAs() === 'publicKey') { + // todo: better rely on the preset this.treatAsValue + if (this.attachment.msgId && this.attachment.id && this.attachment.treatAs([]) === 'publicKey') { // this is encrypted public key - download && decrypt & parse & render const { data } = await this.gmail.attachmentGet(this.attachment.msgId, this.attachment.id); const decrRes = await MsgUtil.decryptMessage({ diff --git a/extension/chrome/elements/compose-modules/compose-render-module.ts b/extension/chrome/elements/compose-modules/compose-render-module.ts index 9fdec41e50f..3b826133da0 100644 --- a/extension/chrome/elements/compose-modules/compose-render-module.ts +++ b/extension/chrome/elements/compose-modules/compose-render-module.ts @@ -450,7 +450,7 @@ export class ComposeRenderModule extends ViewModule { private renderReplySuccessAttachments = (attachments: Attachment[], msgId: string, isEncrypted: boolean) => { const hideAttachmentTypes = this.view.sendBtnModule.popover.choices.richtext ? ['hidden', 'encryptedMsg', 'signature', 'publicKey'] : ['publicKey']; - const renderableAttachments = attachments.filter(attachment => !hideAttachmentTypes.includes(attachment.treatAs())); + const renderableAttachments = attachments.filter(attachment => !hideAttachmentTypes.includes(attachment.treatAs(attachments))); if (renderableAttachments.length) { this.view.S.cached('replied_attachments') .html( diff --git a/extension/chrome/elements/pgp_block_modules/pgp-block-render-module.ts b/extension/chrome/elements/pgp_block_modules/pgp-block-render-module.ts index e7f20d03419..8d7b00fd589 100644 --- a/extension/chrome/elements/pgp_block_modules/pgp-block-render-module.ts +++ b/extension/chrome/elements/pgp_block_modules/pgp-block-render-module.ts @@ -293,7 +293,7 @@ export class PgpBlockViewRenderModule { decryptedContent = this.getEncryptedSubjectText(decoded.subject, isHtml) + decryptedContent; // render encrypted subject in message } for (const attachment of decoded.attachments) { - if (attachment.treatAs() !== 'publicKey') { + if (attachment.treatAs(decoded.attachments) !== 'publicKey') { renderableAttachments.push(attachment); } else { publicKeys.push(attachment.getData().toUtfStr()); diff --git a/extension/js/common/api/email-provider/gmail/gmail.ts b/extension/js/common/api/email-provider/gmail/gmail.ts index 8d4d8a5d5eb..75cad4a34a0 100644 --- a/extension/js/common/api/email-provider/gmail/gmail.ts +++ b/extension/js/common/api/email-provider/gmail/gmail.ts @@ -325,7 +325,7 @@ export class Gmail extends EmailProviderApi implements EmailProviderInterface { return { armored: fromHtmlBody, subject, isPwdMsg }; } for (const attachment of attachments) { - if (attachment.treatAs(!!textBody) === 'encryptedMsg') { + if (attachment.treatAs(attachments, !!textBody) === 'encryptedMsg') { await this.fetchAttachments([attachment], progressCb); const armoredMsg = PgpArmor.clip(attachment.getData().toUtfStr()); if (!armoredMsg) { diff --git a/extension/js/common/core/attachment.ts b/extension/js/common/core/attachment.ts index 929ff529c39..10aa499bbd1 100644 --- a/extension/js/common/core/attachment.ts +++ b/extension/js/common/core/attachment.ts @@ -122,13 +122,24 @@ export class Attachment { throw new Error('Attachment has no data set'); }; - public treatAs = (isBodyEmpty = false): Attachment$treatAs => { + public treatAs = (attachments: Attachment[], isBodyEmpty = false): Attachment$treatAs => { if (this.treatAsValue) { // pre-set return this.treatAsValue; } else if (['PGPexch.htm.pgp', 'PGPMIME version identification', 'Version.txt', 'PGPMIME Versions Identification'].includes(this.name)) { return 'hidden'; // PGPexch.htm.pgp is html alternative of textual body content produced by PGP Desktop and GPG4o - } else if (this.name === 'signature.asc' || this.type === 'application/pgp-signature') { + } else if (this.name === 'signature.asc') { + return 'signature'; + } else if (this.type === 'application/pgp-signature') { + // this may be a signature for an attachment following these patterns: + // sample.name.sig for sample.name.pgp #3448 + // or sample.name.sig for sample.name + if (attachments.length > 1) { + const nameWithoutExtension = Str.getFilenameWithoutExtension(this.name); + if (attachments.some(a => a !== this && (a.name === nameWithoutExtension || Str.getFilenameWithoutExtension(a.name) === nameWithoutExtension))) { + return 'hidden'; + } + } return 'signature'; } else if (!this.name && !this.type.startsWith('image/')) { // this.name may be '' or undefined - catch either diff --git a/extension/js/common/core/common.ts b/extension/js/common/core/common.ts index ebb5a73698d..52e36574b66 100644 --- a/extension/js/common/core/common.ts +++ b/extension/js/common/core/common.ts @@ -196,6 +196,11 @@ export class Str { return rtlCount > lrtCount; }; + // the regex has the most votes https://stackoverflow.com/a/4250408 + public static getFilenameWithoutExtension = (filename: string): string => { + return filename.replace(/\.[^/.]+$/, ''); + }; + private static formatEmailWithOptionalNameEx = ({ email, name }: EmailParts, forceBrackets?: boolean): string => { if (name) { return `${Str.rmSpecialCharsKeepUtf(name, 'ALLOW-SOME')} <${email}>`; diff --git a/extension/js/common/core/mime.ts b/extension/js/common/core/mime.ts index 781df972f4b..c2736bf1cab 100644 --- a/extension/js/common/core/mime.ts +++ b/extension/js/common/core/mime.ts @@ -75,7 +75,7 @@ export class Mime { } for (const file of decoded.attachments) { const isBodyEmpty = decoded.text === '' || decoded.text === '\n'; - const treatAs = file.treatAs(isBodyEmpty); + const treatAs = file.treatAs(decoded.attachments, isBodyEmpty); if (treatAs === 'encryptedMsg') { const armored = PgpArmor.clip(file.getData().toUtfStr()); if (armored) { @@ -258,9 +258,9 @@ export class Mime { contentNode = Mime.newContentNode(MimeBuilder, Object.keys(body)[0], body[Object.keys(body)[0] as 'text/plain' | 'text/html'] || ''); } else { contentNode = new MimeBuilder('multipart/alternative'); - for (const type of Object.keys(body)) { + for (const [type, content] of Object.entries(body)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - contentNode.appendChild(Mime.newContentNode(MimeBuilder, type, body[type]!.toString())); // already present, that's why part of for loop + contentNode.appendChild(Mime.newContentNode(MimeBuilder, type, content!.toString())); // already present, that's why part of for loop } } rootNode.appendChild(contentNode); @@ -306,9 +306,9 @@ export class Mime { rootNode.addHeader(key, headers[key]); } const bodyNodes = new MimeBuilder('multipart/alternative'); - for (const type of Object.keys(body)) { + for (const [type, content] of Object.entries(body)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - bodyNodes.appendChild(Mime.newContentNode(MimeBuilder, type, body[type]!.toString())); + bodyNodes.appendChild(Mime.newContentNode(MimeBuilder, type, content!.toString())); } const signedContentNode = new MimeBuilder('multipart/mixed'); signedContentNode.appendChild(bodyNodes); diff --git a/extension/js/common/core/msg-block-parser.ts b/extension/js/common/core/msg-block-parser.ts index 87b788636b3..b1bc7be7d22 100644 --- a/extension/js/common/core/msg-block-parser.ts +++ b/extension/js/common/core/msg-block-parser.ts @@ -82,7 +82,7 @@ export class MsgBlockParser { blocks.push(MsgBlock.fromContent('decryptedHtml', Str.escapeTextAsRenderableHtml(Buf.with(decryptedContent).toUtfStr()))); // escaped mime text as html } for (const attachment of decoded.attachments) { - if (attachment.treatAs() === 'publicKey') { + if (attachment.treatAs(decoded.attachments) === 'publicKey') { await MsgBlockParser.pushArmoredPubkeysToBlocks([attachment.getData().toUtfStr()], blocks); } else { blocks.push( diff --git a/extension/js/content_scripts/webmail/gmail-element-replacer.ts b/extension/js/content_scripts/webmail/gmail-element-replacer.ts index 76a5ad7012e..8e45220a430 100644 --- a/extension/js/content_scripts/webmail/gmail-element-replacer.ts +++ b/extension/js/content_scripts/webmail/gmail-element-replacer.ts @@ -432,7 +432,7 @@ export class GmailElementReplacer implements WebmailElementReplacer { attachmentsContainerInner.parent().find(this.sel.numberOfAttachments).hide(); let nRenderedAttachments = attachmentMetas.length; for (const a of attachmentMetas) { - const treatAs = a.treatAs(isBodyEmpty); + const treatAs = a.treatAs(attachmentMetas, isBodyEmpty); // todo - [same name + not processed].first() ... What if attachment metas are out of order compared to how gmail shows it? And have the same name? const attachmentSel = this.filterAttachments( attachmentsContainerInner.children().not('.attachment_processed'), diff --git a/test/source/mock/google/exported-messages/message-export-1869220e0c8f16dd.json b/test/source/mock/google/exported-messages/message-export-1869220e0c8f16dd.json new file mode 100644 index 00000000000..49b1818e8f9 --- /dev/null +++ b/test/source/mock/google/exported-messages/message-export-1869220e0c8f16dd.json @@ -0,0 +1,133 @@ +{ + "acctEmail": "ci.tests.gmail@flowcrypt.test", + "full": { + "id": "1869220e0c8f16dd", + "threadId": "1869220e0c8f16dd", + "labelIds": ["IMPORTANT", "CATEGORY_PERSONAL", "INBOX"], + "snippet": "-----BEGIN PGP MESSAGE----- Version: ProtonMail wV4DeWfgCtVtdnoSAQdAPNQhPJG8if4F6R6Dneng7TfppSVPQYHsKYCqoKKD 9W8wDO6xf08jS+Sn7QJcs/N/5so8bfppkTmx9xgEly5JIhwyrcIGp7R/ClN6 0hW9YzzB0sHjAeNkIAPLOMRhW+", + "payload": { + "partId": "", + "mimeType": "multipart/mixed", + "filename": "", + "headers": [ + { + "name": "Date", + "value": "Mon, 27 Feb 2023 09:07:46 +0000" + }, + { + "name": "To", + "value": "ci.tests.gmail@flowcrypt.dev" + }, + { + "name": "From", + "value": "schlemazle@proton.me" + }, + { + "name": "Subject", + "value": "Inline signed and encrypted" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "Content-Type", + "value": "multipart/mixed; boundary=\"b1_APtjrArD3xyvepenMGInm3lxJB3YfIvNBM8MvjwSkc0\"" + } + ], + "body": { + "size": 0 + }, + "parts": [ + { + "partId": "0", + "mimeType": "text/plain", + "filename": "", + "headers": [ + { + "name": "Content-Type", + "value": "text/plain; charset=utf-8" + }, + { + "name": "Content-Transfer-Encoding", + "value": "base64" + } + ], + "body": { + "size": 1154, + "data": "LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tDQpWZXJzaW9uOiBQcm90b25NYWlsDQoNCndWNERlV2ZnQ3RWdGRub1NBUWRBUE5RaFBKRzhpZjRGNlI2RG5lbmc3VGZwcFNWUFFZSHNLWUNxb0tLRA0KOVc4d0RPNnhmMDhqUytTbjdRSmNzL04vNXNvOGJmcHBrVG14OXhnRWx5NUpJaHd5cmNJR3A3Ui9DbE42DQowaFc5WXp6QjBzSGpBZU5rSUFQTE9NUmhXK0F2WW41aEx3UXphZWNBK0pmbXZIY0pzWlZESlNVbGxTNUUNCk9ENnBUdUtHb0tVUEF1RGdpbkF5cXVvRlI4WlFOSVFwUUk5ZUt5U2JXMXpWemNMWmVsMS9XeEZ4VWdXZQ0KTndFMHhRUWl2Kys4Z0FWcUQ1UkJEZDZGdjY5RlpCMVBNbHc1QmpDaTdpQnp3VXZzZ3UwVW1nZFErZ1ozDQplSTZXMkxSTVZYUUNEd0t3OTcxZDBMMWpxNmZEYmtLTklYVHVRbEY4b2xXeVRmd20zK3BYdmdidVFCdEkNCmhkaW9ndWEwcElEQkdQbDlScmdENFNUcjJnTUJFMk8yWWVJZG5NQVVWZE5ZelVSOUFxYkpnQm5NdTU2Lw0KOHJkcS9oTFpRWEpyL3hzdTVEeUgxbkd1Z1JLU0Y2dDVkSlpZSkQvN1hNNENKWkJ6OHZrWENjcTdtTmdSDQp3MWsrMGZZVUJjeXNxWFFNdTNOWlo5OVU0NHQxY25WcW9KNnowblFHSmJLZGhIditFQTMxU1VpV3lJQnANCmxWOUVnTmViM0s4TG9KUE9icGdqVWswTjJuS0JhRXl4UDhRZ2FDQ1psajV6ZDVYbEtFRE5IdUMzR2kyUw0KbkQvYmt4Y2ZsTkhsWkR2MGIxWEdNcUlCZExEalVXTUV2ZXdzR3VwdFRBQ25IazR4SDB6MWVRckNNT3pPDQphbHkrYWY4anRFMVo4NDBMbzJRb1U3M2VEUjdaVCtlV2hOQ1hYVDNSZ2w2VmVSRWRteVp2VG1FTXVPMkINCmluTG91MU1JeWxoc1lPZC9vVzcwUW9OcUtMU3EzRG9nOElNM2pqaGpNN0s0ZEZBVFF0cnVaWC9BVXFRSg0KNG00VDZRclBEL21lOFR5SzBRV1Y0UzREZGJhVVVxUVViUndKa2hIN0d6VGxmSDVHNnQycnFmaWRyczhjDQp4bkVtSXZvVUc3Wkp0UEFtWW9USlRDZ24zOHc2SWMxVElMTWZKRFlQMWZkYjI5S25lZXpOSHI1QkwzR08NCndmbTZ1aTM3VGZTNmMvaFI2WDEvalB6dDQ0R09Yb3l0N0RnaFgvVXFYcTJ0cDJjcjFBTUhRWlEwSXFxTA0KdUpEakVhWHZpOHFzZStvRGdTWityaDRQNEsrL01vS1phdUZwOUtrYjZjd0VEU2JvUkxZSFFOYkdOL25xDQpQZkJYN0hDN1N6dXoNCj1WUGd5DQotLS0tLUVORCBQR1AgTUVTU0FHRS0tLS0tDQo=" + } + }, + { + "partId": "1", + "mimeType": "application/octet-stream", + "filename": "publickey - schlemazle@proton.me - 0xA5140005.asc.pgp", + "headers": [ + { + "name": "Content-Type", + "value": "application/octet-stream; name=\"publickey - schlemazle@proton.me - 0xA5140005.asc.pgp\"" + }, + { + "name": "Content-Transfer-Encoding", + "value": "base64" + }, + { + "name": "Content-Disposition", + "value": "attachment; filename=\"publickey - schlemazle@proton.me - 0xA5140005.asc.pgp\"" + } + ], + "body": { + "attachmentId": "ANGjdJ9pRNT3HoxqlbhmP4GxrLjMYvpaEX6PFy-MY0IdV0Naq9mneeDsesG3uIOQAx4_DIWU86VHmFdB1eST67tBgRBcQIjfunLDrcSkijWQmcJXrZ225LRBCvmNq3vQwRoDqen4PVYfFdlasrHLwZ4oh5Cj4wpRZd5ItJl6BuUdvVPmNunbd5cGObdjpQEtSDQnWdpJjbK7XhUOrCisxjakoT2F0onnYZkf7jIvdVP_wN0L4QogsBYahSyq07mkAeZR2NgQowa2O4-_xXakzO1FbkN1-xVQwX31Td3-8I7bv5snlLtV8QwgK3-hk403lJ4nTTUJ9UqxVc5no65r7SnBDwD_xZhO7oClVCK371VLpQsy0bhMZVWgcbya2VfwvIQlFl6_6OOusQRU0vnE", + "size": 3291 + } + }, + { + "partId": "2", + "mimeType": "application/pgp-signature", + "filename": "publickey - schlemazle@proton.me - 0xA5140005.asc.sig", + "headers": [ + { + "name": "Content-Type", + "value": "application/pgp-signature; name=\"publickey - schlemazle@proton.me - 0xA5140005.asc.sig\"" + }, + { + "name": "Content-Transfer-Encoding", + "value": "base64" + }, + { + "name": "Content-Disposition", + "value": "attachment; filename=\"publickey - schlemazle@proton.me - 0xA5140005.asc.sig\"" + } + ], + "body": { + "attachmentId": "ANGjdJ8ObnISqVY0JigN1hg_P91vvKZXr6dr80PrwuuPtOhSvx4PPXRgV6T5hKGO7_3lfwh_pQKTWpUmFwUMhetsesG_kflx2i3WrkK1xkoEFSawR42PrXO_B4wXBamt3mSbT4G7ZZPTE98Ge2GUValy4y04aJEKUgRCcuWGs_t5vmoAaYSuG5WYSWUaFdECRudxUY8PdChMETMLMbb4QcAupoB0T3H4qVMoNGre8AMWfE1iz9jXxSIcd6EBuLQmzSw2ab1r_16JLtJeiTSSIyjo-vv1szsBAdhliKiff1CdMo4cH4G5P89Z9pIqhkuBISwHTGDzmdiWGFLfiFoLry4VLQjT0wgDph9XFig8kpn2-4dvz6aUlkuuglc528ZtzwmLFGhnaJTTn0aa-uMF", + "size": 566 + } + } + ] + }, + "sizeEstimate": 11617, + "historyId": "2491472", + "internalDate": "1677488866000" + }, + "attachments": { + "ANGjdJ9pRNT3HoxqlbhmP4GxrLjMYvpaEX6PFy-MY0IdV0Naq9mneeDsesG3uIOQAx4_DIWU86VHmFdB1eST67tBgRBcQIjfunLDrcSkijWQmcJXrZ225LRBCvmNq3vQwRoDqen4PVYfFdlasrHLwZ4oh5Cj4wpRZd5ItJl6BuUdvVPmNunbd5cGObdjpQEtSDQnWdpJjbK7XhUOrCisxjakoT2F0onnYZkf7jIvdVP_wN0L4QogsBYahSyq07mkAeZR2NgQowa2O4-_xXakzO1FbkN1-xVQwX31Td3-8I7bv5snlLtV8QwgK3-hk403lJ4nTTUJ9UqxVc5no65r7SnBDwD_xZhO7oClVCK371VLpQsy0bhMZVWgcbya2VfwvIQlFl6_6OOusQRU0vnE": { + "data": "wV4DeWfgCtVtdnoSAQdA4kGwTDSb1fd4v2M6YCRcuX2KHNvHdZI5ued1okohXwUw5FaloTIHJIiOx7FE8-JM8hai6KscbNMLfYWGCP2H9xv-CYS9J4nvZ1OuwJWDVbQJ0su4AajhNdmX8to5tBecKw-vxSsfASKChm4mpzULjxqRb--t5aQXwENDS-5YteWwXreKGyMSWfW4V5CIR3NxruzzzFnuNyjxkPw_-DZW0rZx8225IdA5lGF6PeYh74Xdrl8Aq9C2IxpgxPTWtaAxpHJ69VfMCm2tTKOOfU4j4G_14nl9kGxMiUJuL-NQ6c9Yj3kVlTdc7Q5Cxas7szh4D4uYzsoIHioWUsbwpJgZIursqskj0O-Q2oE45ySo-VT0Eq82t2bcEZo7oH3Clb5-EdZHPWMZHGcJoiRJyGcU76qYjC8zWrsqSotZtPlRTteujIk_d82172tkd1s7gjKWHZ_cb4Pm1tET3akPXWrbcnt1Q0hyK33tjZ9i8peKdAdOcy3A8VS5EBKbP62074Lnc-jXTuRjiwMi7iBv6sVxII5wbE-58Pbt0Jg3WnnINdWPiakNcYIrW94KLjwgWUmzkGQNBV6ISKm2qKdR7KpkDBN3v5NRCFPmimvqgT1dfAcol7oVHVNIAO4iyTuiX7oyN7-GyGkmvMovLDGzn_rD2r88jfFKEBXsYe5GOUCH4wIzS0G5SzKWBTA5PrQm2KicRkdIceYFk9dh7EErvF201MuKc--Sok1cN_CE8PSSSC14y25TqOq0GwvehPrnmkyi8rVwKazwLhBqoSaxes78Me99I_aU7HvY2HZA1_xSVdFsGRvjKN1Q9N-wWgbnE4RDXcVq6l5WW61kRLyAHFk_9_b6NSjq6618dMrIM2pilu3LBB0Jk2Sic7rMRvfW9bFk4nkClIr6hdudBJPBh_dQUKhJny8gCmjbybLQ_4hw3Iv0bhw00De4uvEJAgKyOX5uBkc3bE78qBa1RHxjGxcNi8Fb5jgow4rzLQn9xErvtESMwH3pmCwfEW2tFnyYtFgk6lkdp6uQR7T9lLwaj_R8FSN9fFN1N3oY1YjpzV2CFM7iifMCLbotts8mrJzJcXgE3_iUQLVKgUllAF0ey5DDPwjxcbOMcm_9qAFqfbbkb5c9x7BFFdYJeJ8zAyQoJOVZxBD_rKWtS1izfuy5fhicokRpMIq6P0NPTcD6Q1PWG93852iTlZYE_TOqp_G8Tc9HpQE5cRQChMVzV_4pHeqqhWn8-09IvGvfrq5Ew_zuqFXUGyjQszBHyPCwXBHJ8dMifkZZlyyG2Gius9IRM8Snu0XU6hfE3WvRu4KZDvNYs3J57RCtUGVI9hKI1yjgJWpjCC4S851JgJZUFZVySiT7u4dcJE6Cr66zcM2NVDF_1jCQsMTjVrMxVHLu2ddCn3QS1DTZcA8nqCYcafVPwklopnM_mJN5L3z-uTQIlfc9DnB0zTeBU-j7jGumAtoMfNkvsd24_3H5-0d8evzbmhRqCmIouxgfSYyqESRw29D6BFdW1L-a9AjMFONeChtKNoPA-fJAUBV8TneVapkiam4jPfzUyNt592D8RIv2byZj1b2fVsmroeyPTabwpHdzNdLV99WMCcBiSiFnYa_jH_499zzy4Qur2FICzVVOZoksJ2GdiY_QfSbtk2C9_UK0SRgV3ymqoGknPk6QRQizH5Bo1iqDpelY64J3XoHulr8BPx89hDpMusS9IJeqNx1WacdPgaJWoMY1ghFHMZBWRNWxM1DhswjBZjLwP0h-Fuf8nolURv_oQAAjaBX2s1nUKAUdeGhsVgn0LcNjJtNiNo-UUOlcrKwBo3TEKwKDHyEtw_81zvtdseyyJgmDo5K_UcirmcsLiOzYOOsLzl6d9LQ_pXIxkv5NUtUUlqL2I7kw4UNUCT56Ms1Cd25KDy8cVb7hFHQxJvkxvYYhoxurFDfPplI3sK1IVNasSsJZtf-qaGI6_gewTWdRG6SoVm9Y8wt71Tm_83dIRHWDdXHF_75q4bekbQ94o6m7KTYtNN0ULnPZLk1NNy971Sbgf1A28-vPHRXdgG-q8zT7soT6pO-LWEaZCHBsvQy3u30eTWG9weJauwt4LEPNSH2-uEZdD5nteLNUujR9zhQp0v6do5h69-RIlUKcQGWnh_rIs8QMLNxq164ZqBC3h30a-gVjhxUVyrz6-ETR2jdt4eIEZ9p-TqkmY-usqlvbIKaHEavOy3KpR3cFUO2hOHA5-oaqGWLUJRStKKSbcu1cKDAVLiSakS1egAtAsbzcLunEKpP69-EpyXexlnx0mPfzz1xFt2hy5kY_FQXjSvJsxUlsCqUy6QtTaz9rKx0Uli9RRfsZzYhKQRjcViHRVPuna-8FlIrN-J5qZtaG8HEboFNkOpmWVF0XnH-ZKlppA5hI_hDr7aO0FFJ7qOSXLcB6TrlMIMyoMhONqfnEfkyIe8J6Ri71Yt2qc7KjpMj5rjrVkN-TUWYOn65YFT_e4Fz5qVjsNh-WrjcE5qdrvZZOimcj_FmQmWMVgW39rcscgHZRS5Ehp4ak0gAqYhyhoV-_T3ADwSVoc5K3OxJ6dLT9WHI6L9tZ60XdFIyhWnr4-BS3rP9nSUNuPkVcx2QfslHSESX-GBs0sQh5Yk_pxq2zgW83ZQjRjgvp0x4DBkp8p2SnOSdE9eHa2LZr1nZiVpLMI3ALDxhXd9SVUOVxcAS7A_y97OMHY9AA4Fk7zDxQAUyARUPGgmUcCLrEGSyyGZzTIJz-mcd2ZK2ReYcos-nHxPO-EFVddVP1qkXmMHRytBnjWjngKZPFgCY3a2U64PicJxymjq_e-jVh7uMoKjcpVQxCxZNwdwHzNf37NXERbeQkV5hPPc-IH3YFGlxowXPLTZCx6tCXHrd9uvjGTgr9yrTi65I9cIfvYHW31yKtor62IqgYWvrrckoiFGNgu1L99-auDzJwxZSSksBZnGCT7VYfhmgiGX61j1x3CBiHq1v53YtMX9nx-YXkFXHjcJ_rZhJOyf4waKOnqSmyWLYUjbQAezuZOvULbwPZoHNWK36fZWezoEXP8Pz2GwahZ2UdPZHtChQBwMYEHv1lnl4EJmyhul9G6KW4W7gforI4yVHv8zzy4X0dGb0-mwYgUvS8BuPyiaa3kPh4MTR_mBGuBiXDmQeu7lNz4eLhXy2o-zDeJytSmtkBM-D_XbzlEUeWMouxpmQN56cysJSTcenLPWwpYQQoOwQ_ZfuqUSo4d9adFPTKSCiSNLITBV_5kSc2Go6TkVZ3_AbNniwcjs8wxjpx2b7Q-GLLqSMDTFyN_Y76yBB00sX9x0q7wW6akZ8fTTSEKaxCyLVch1x1UUD4LZMwUWQcWQzaMU5bAXJnZsnXNPZJsZ6Xzf2V5g28n3TS2G_VCysWVNn93VzcOY2bVoJUwPJIiT_H0NfC5HId4QsSeOKg317DzOYrHTJs8cyMO0E1yM-9XhAfzhlUFwi_6YMP1UuV7kCkI1rZ2AMR3I_6_QAT9YP0rnRlAyvLjRpnEVtDTLuKDWhJYY8ACnh2_jTanrU0L_ShI2pbkc6L84wjHZPzCUsE05x5j9gExDiwGVqOZ5FkIuY5jimXUTtyCW7YKruFtkTwHyxlAHFY4gek_F_lqcm1vcmhi3L2rQb6pHIwwkRpSoMSEBiRsRbjrkvwFJnnKNF4VbbnQQWeVLHVRR3vVc6uWaLULcaUXSpRvIL4S9IUGe2lVOveQeBDF4exW27BiUXSFByxHvFFH8Z3tgTgte4Wi0XPi2ZJ4flUBlSnJZrdg3F9Y92_Hg2HzBmOPZlWTXTFZR54cOIjNjF-hEr8zulha1wFsPz1ygOBmp6ENWxxhBNo1K-Cp2O6Ik72HLHLAj6L8T6heuX6Tmr2mgQl87EgpGGEV1EObbItEh_xCCkAjoDaJPgfDOFNcIhgI608FoWJX98dAx4gRLoxXhtYH_8rV1o5L8ech75UnhIounm_KPa55KjijCIV4q-5dozVrs9y9x4p2tB0tdK-Hl8RJlgpoX3HHfZe23KmrNgAvhMOh25_pE_Wc4AD0CxBn4vVlJCYIM5wKDtlEr3YhhLzCtYo4bIJV-3J-FexP2Ti8z2Wez252NnivdX56ohhOlJbKdJFt0BPs0_XfOKk69vZ4xLF_tJUNkv3vz_R8TDCNaiVvvRckVqdTN8Cemqn-5YhBvdN8V1SslX-tpu2G950HAJ3WAI_J4B72M9-4maAYC1xcRvlv-VZOr30GDf_o7Zd-kmziw-nXMI3DJSUhXHQAeZyjvbQD5PSMtD8J6hNSS9zsSQdXorohWaJVSZMZqAfH4FWW1tMDKdbax8Z-3jsBJwNT21tocRkTj7d5ImOCmu9NzoXqL3t1P6Z9B94", + "size": 3291 + }, + "ANGjdJ8ObnISqVY0JigN1hg_P91vvKZXr6dr80PrwuuPtOhSvx4PPXRgV6T5hKGO7_3lfwh_pQKTWpUmFwUMhetsesG_kflx2i3WrkK1xkoEFSawR42PrXO_B4wXBamt3mSbT4G7ZZPTE98Ge2GUValy4y04aJEKUgRCcuWGs_t5vmoAaYSuG5WYSWUaFdECRudxUY8PdChMETMLMbb4QcAupoB0T3H4qVMoNGre8AMWfE1iz9jXxSIcd6EBuLQmzSw2ab1r_16JLtJeiTSSIyjo-vv1szsBAdhliKiff1CdMo4cH4G5P89Z9pIqhkuBISwHTGDzmdiWGFLfiFoLry4VLQjT0wgDph9XFig8kpn2-4dvz6aUlkuuglc528ZtzwmLFGhnaJTTn0aa-uMF": { + "data": "wsFzBAABCAAnBQJj_HLECRBhbVlrwgZdSBYhBKUUAAVXUrRPueoIlWFtWWvCBl1IAABeiw__ZzonIepMwDlEYE4Lk0pUzt4JVHDFVWq4OwyGg-jSDK1EV0C5qAS-UXHXc10wKhuj43VE7loAbCTbrVxP06j7pnzvSm3cMs9UStgqAYf9D6dQFULlz4KSwJ0MvIThP-3FkfNUUe8aziOIue_meA_ajC98f4rKbIC48Ee5T66krSOh6gGBXRMhsWojJSHEUPI1yGD7NDBr3xKtdvyCufRoIWMXOfED3nSQMZJjJFhJlCxI_3TtmSumVRhaxU8WjV3ceCOcovHhI2YZ2KAL3oOAgcn-AQ79NkWeCWHnqOUJZk1ZCIi5exSr9Jv1NP6tb2nzLwPNDwf4OScLXX3KBZTlr2uqStWPQ1Gjj5ovP-gPzoUchATXM89bGYZVSnJdmTL3gYPmnuXC6GEr5EJK5k0Kc3bVN__ysdx0aHEImY-0LyoVTI_24ULOQm_oUX_Ltty6CgIj40BM2vr8zvJ2oIeYEZwGbszcQip0njJ9ucGDchUjppEBjd7KglYkA30e-UyLfeBggEwGVtELRxDwIZ6G953LQAu_fRezkQ_IlbDjD0mavZv7tvZunV_RGelCmBRj6MH4qjBOReIRuWBlRwoaoP0ugb_Ma6oMOzZiw5bAqRzlcHG95fQNtDWFG50XSK70S_-Fqk7q9RaK3PKJ0Bc5QRMdr1xrpLP5ju0V2saUuOk", + "size": 566 + } + }, + "raw": { + "id": "1869220e0c8f16dd", + "threadId": "1869220e0c8f16dd", + "labelIds": ["IMPORTANT", "CATEGORY_PERSONAL", "INBOX"], + "snippet": "-----BEGIN PGP MESSAGE----- Version: ProtonMail wV4DeWfgCtVtdnoSAQdAPNQhPJG8if4F6R6Dneng7TfppSVPQYHsKYCqoKKD 9W8wDO6xf08jS+Sn7QJcs/N/5so8bfppkTmx9xgEly5JIhwyrcIGp7R/ClN6 0hW9YzzB0sHjAeNkIAPLOMRhW+", + "sizeEstimate": 11617, + "raw": "RGVsaXZlcmVkLVRvOiBjaS50ZXN0cy5nbWFpbEBmbG93Y3J5cHQuZGV2DQpSZWNlaXZlZDogYnkgMjAwMjphMTc6OTA3Ojc4NTpiMDo4NGM6ZTczYzplZTc2IHdpdGggU01UUCBpZCB4ZDVjc3AyOTAyMDc2ZWpiOw0KICAgICAgICBNb24sIDI3IEZlYiAyMDIzIDAxOjA3OjU0IC0wODAwIChQU1QpDQpYLUdvb2dsZS1TbXRwLVNvdXJjZTogQUs3c2V0K0NUQUFDMUZDNHlZNXVmcmhKcWJFMXN2czhsK2Y3N2JKeDF5S0xxV29qenA1YUhqZXRLRC8xZURGdGppOVloZ1lQNEwyQg0KWC1SZWNlaXZlZDogYnkgMjAwMjphMDU6NjAwYzoxZDllOmIwOjNkYzo0MDQyOjVjMjEgd2l0aCBTTVRQIGlkIHAzMC0yMDAyMGEwNTYwMGMxZDllMDBiMDAzZGM0MDQyNWMyMW1yMTg5MTE1MzB3bXMuNi4xNjc3NDg4ODczODIwOw0KICAgICAgICBNb24sIDI3IEZlYiAyMDIzIDAxOjA3OjUzIC0wODAwIChQU1QpDQpBUkMtU2VhbDogaT0xOyBhPXJzYS1zaGEyNTY7IHQ9MTY3NzQ4ODg3MzsgY3Y9bm9uZTsNCiAgICAgICAgZD1nb29nbGUuY29tOyBzPWFyYy0yMDE2MDgxNjsNCiAgICAgICAgYj1qdlFGYVFaQWJaUWdZQzNYaVFYbUlKbVVIQWt0bWsrZEh5NXZCYlZISVgvVitHWCtZMWZGSzRDaU5YakpYdzdTOG8NCiAgICAgICAgIFVIbkhmckxBYW1RVm5YR0lDV1RMWkFjeGFDeDd3YldsWkFjb3JKeUFVZjJIa05oZ1BUR1J5SmhsblozTTlJdWZMSmVsDQogICAgICAgICAveGdtcWw4M09XKytjaUM4MitPK3dERGNRSy9EbEVWejZLZTBUbUFwOHhCK0Q0TlRhSms4Rjh1RkJZcUcwRHlJd2tCQw0KICAgICAgICAgQnpIVU03M1BjQmNqamRGYzdRT3pGd1R2aGZadit6SmFiSjc2RDVydGh2T0FoMHM0SG12eDdpTXJhbFg5U0VvbDlPd2gNCiAgICAgICAgIGlIc1FXWEJDNHQwSE82bndLTTh3MXFZMVhCQnRLem9GcURzbUZtYkttUGdDQk1vK2ovUFhkb1RPSUUwRWlibys1MVduDQogICAgICAgICBhM3F3PT0NCkFSQy1NZXNzYWdlLVNpZ25hdHVyZTogaT0xOyBhPXJzYS1zaGEyNTY7IGM9cmVsYXhlZC9yZWxheGVkOyBkPWdvb2dsZS5jb207IHM9YXJjLTIwMTYwODE2Ow0KICAgICAgICBoPW1pbWUtdmVyc2lvbjpmZWVkYmFjay1pZDptZXNzYWdlLWlkOnN1YmplY3Q6ZnJvbTp0bzpka2ltLXNpZ25hdHVyZQ0KICAgICAgICAgOmRhdGU7DQogICAgICAgIGJoPW5tK3FzYTBFbmtRUGY1UmpYWkRLeVVyQnJSNTlNa0R0YW5pTUh2bUUyczA9Ow0KICAgICAgICBiPUo5NVFUU3ZrcTZtTjFKTEQ1aFBwbXNmQkNCVnhjckRDMnk4dVhpdnFUQlBzUGVtSlJkZGxOZFB4MDdReXByVHphNg0KICAgICAgICAgNm5kWStPeldXUDNTaXE3Y3UrQmVVb2U4aWgyaUxRWnhRNGIyNVRKZ2dic1R5VmFRUmJjYzYwVm9kTUdSZGgyMnRoRnQNCiAgICAgICAgIHhWMFhubmFrVzBkZmtJVFBJRlp0WjF4VWVObnhUejZ6QzhldGs5TzR6VDZNdmd4YWpCWDBZR2FIU01UNjRLTUQ3Vnl3DQogICAgICAgICBQcUFuekpHYzU2blZ6WnF5V2YwSUVXNnN6MDRhVnpTdlFxb24zNm5rdEFxQXkyOWVwOGpTeDh2RnFySVl5QkNyMk82Zg0KICAgICAgICAgK3RaTXdYSXNFYmFvdlA4cnc0dXhSanhBdFBySVg5NitGbFlvWWl3NjFhc0lJd01Kek9NSm9Pa1BpaE1WcDNYTTEyckUNCiAgICAgICAgIEx6TVE9PQ0KQVJDLUF1dGhlbnRpY2F0aW9uLVJlc3VsdHM6IGk9MTsgbXguZ29vZ2xlLmNvbTsNCiAgICAgICBka2ltPXBhc3MgaGVhZGVyLmk9QHByb3Rvbi5tZSBoZWFkZXIucz1wcm90b25tYWlsIGhlYWRlci5iPVBWN2hCSllrOw0KICAgICAgIHNwZj1wYXNzIChnb29nbGUuY29tOiBkb21haW4gb2Ygc2NobGVtYXpsZUBwcm90b24ubWUgZGVzaWduYXRlcyAxODUuNzAuNDMuMTkgYXMgcGVybWl0dGVkIHNlbmRlcikgc210cC5tYWlsZnJvbT1zY2hsZW1hemxlQHByb3Rvbi5tZTsNCiAgICAgICBkbWFyYz1wYXNzIChwPVFVQVJBTlRJTkUgc3A9UVVBUkFOVElORSBkaXM9Tk9ORSkgaGVhZGVyLmZyb209cHJvdG9uLm1lDQpSZXR1cm4tUGF0aDogPHNjaGxlbWF6bGVAcHJvdG9uLm1lPg0KUmVjZWl2ZWQ6IGZyb20gbWFpbC00MzE5LnByb3Rvbm1haWwuY2ggKG1haWwtNDMxOS5wcm90b25tYWlsLmNoLiBbMTg1LjcwLjQzLjE5XSkNCiAgICAgICAgYnkgbXguZ29vZ2xlLmNvbSB3aXRoIEVTTVRQUyBpZCBlMjAtMjAwMjBhMDU2MDBjMTNkNDAwYjAwM2RjM2YxOTgwNGRzaTUzODcyMDl3bWcuMjAwLjIwMjMuMDIuMjcuMDEuMDcuNTMNCiAgICAgICAgZm9yIDxjaS50ZXN0cy5nbWFpbEBmbG93Y3J5cHQuZGV2Pg0KICAgICAgICAodmVyc2lvbj1UTFMxXzMgY2lwaGVyPVRMU19BRVNfMjU2X0dDTV9TSEEzODQgYml0cz0yNTYvMjU2KTsNCiAgICAgICAgTW9uLCAyNyBGZWIgMjAyMyAwMTowNzo1MyAtMDgwMCAoUFNUKQ0KUmVjZWl2ZWQtU1BGOiBwYXNzIChnb29nbGUuY29tOiBkb21haW4gb2Ygc2NobGVtYXpsZUBwcm90b24ubWUgZGVzaWduYXRlcyAxODUuNzAuNDMuMTkgYXMgcGVybWl0dGVkIHNlbmRlcikgY2xpZW50LWlwPTE4NS43MC40My4xOTsNCkF1dGhlbnRpY2F0aW9uLVJlc3VsdHM6IG14Lmdvb2dsZS5jb207DQogICAgICAgZGtpbT1wYXNzIGhlYWRlci5pPUBwcm90b24ubWUgaGVhZGVyLnM9cHJvdG9ubWFpbCBoZWFkZXIuYj1QVjdoQkpZazsNCiAgICAgICBzcGY9cGFzcyAoZ29vZ2xlLmNvbTogZG9tYWluIG9mIHNjaGxlbWF6bGVAcHJvdG9uLm1lIGRlc2lnbmF0ZXMgMTg1LjcwLjQzLjE5IGFzIHBlcm1pdHRlZCBzZW5kZXIpIHNtdHAubWFpbGZyb209c2NobGVtYXpsZUBwcm90b24ubWU7DQogICAgICAgZG1hcmM9cGFzcyAocD1RVUFSQU5USU5FIHNwPVFVQVJBTlRJTkUgZGlzPU5PTkUpIGhlYWRlci5mcm9tPXByb3Rvbi5tZQ0KRGF0ZTogTW9uLCAyNyBGZWIgMjAyMyAwOTowNzo0NiArMDAwMA0KREtJTS1TaWduYXR1cmU6IHY9MTsgYT1yc2Etc2hhMjU2OyBjPXJlbGF4ZWQvcmVsYXhlZDsgZD1wcm90b24ubWU7DQoJcz1wcm90b25tYWlsOyB0PTE2Nzc0ODg4NzM7IHg9MTY3Nzc0ODA3MzsNCgliaD1ubStxc2EwRW5rUVBmNVJqWFpES3lVckJyUjU5TWtEdGFuaU1Idm1FMnMwPTsNCgloPURhdGU6VG86RnJvbTpTdWJqZWN0Ok1lc3NhZ2UtSUQ6RmVlZGJhY2stSUQ6RnJvbTpUbzpDYzpEYXRlOg0KCSBTdWJqZWN0OlJlcGx5LVRvOkZlZWRiYWNrLUlEOk1lc3NhZ2UtSUQ6QklNSS1TZWxlY3RvcjsNCgliPVBWN2hCSllrbEhqUjY2YXdhSjhEMmhQbEtTVmhBS3NyTnk3U3dHdDFUQkZMcHpMUXovWDZ1NENrR3U4cTAxa3BiDQoJIG0yYlFiY0NhcFQrM00zQzY1UU9Hc0k0N2VXaGFPM29kcFYyRWMyS0hvV0dGSlRlUHlQV2thRzRZSkd4S2FyM2krTg0KCSBRa1hqdDYxS0pDMVVQNWdRRFk5NnlvNmw4N21JYU9LY1VoSUt1dnIyQzJtSUo3elJQc3daZmZySCtUYW1yaVdHa2cNCgkgMGNGbGo5RjNLSmY0MnpDN3JhV1VmSXRnZ0o2dzdjUjJvSUQvR0xEV2k5OG4wbW0zSk1SZ3BOLzVKME1mNS9IeWtEDQoJIHVQckp6SXZoQTdOV3MrNEoxVHg1VEhWM2R2TEl5QUQ5aGdmUzRsaG1TTUdIYlVTeVpLMkNmaEJRcUs4ZVd3cU9GQg0KCSB4aUJuSFZXSmFPTnVnPT0NClRvOiAiY2kudGVzdHMuZ21haWxAZmxvd2NyeXB0LmRldiIgPGNpLnRlc3RzLmdtYWlsQGZsb3djcnlwdC5kZXY-DQpGcm9tOiBzY2hsZW1hemxlIDxzY2hsZW1hemxlQHByb3Rvbi5tZT4NClN1YmplY3Q6IElubGluZSBzaWduZWQgYW5kIGVuY3J5cHRlZA0KTWVzc2FnZS1JRDogPDlFal9jSDY3UnZmZnJPTDlrSnNxdGt5YUxQbVpwTDJTUmluN3ZkRXIzX29KY013VEEyaEJCRkhtc3dpZWdKSlZrN2UzM0t5WGJBVS1zeU9GemtRSFplS0ZtME5rUFNUTjBYc0JSNHJNR0VzPUBwcm90b24ubWU-DQpGZWVkYmFjay1JRDogNjgwNDkzMjU6dXNlcjpwcm90b24NCk1JTUUtVmVyc2lvbjogMS4wDQpDb250ZW50LVR5cGU6IG11bHRpcGFydC9taXhlZDsNCiBib3VuZGFyeT0iYjFfQVB0anJBckQzeHl2ZXBlbk1HSW5tM2x4SkIzWWZJdk5CTThNdmp3U2tjMCINCg0KVGhpcyBpcyBhIG11bHRpLXBhcnQgbWVzc2FnZSBpbiBNSU1FIGZvcm1hdC4NCg0KLS1iMV9BUHRqckFyRDN4eXZlcGVuTUdJbm0zbHhKQjNZZkl2TkJNOE12andTa2MwDQpDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9dXRmLTgNCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJhc2U2NA0KDQpMUzB0TFMxQ1JVZEpUaUJRUjFBZ1RVVlRVMEZIUlMwdExTMHREUXBXWlhKemFXOXVPaUJRY205MGIyNU5ZV2xzRFFvTkNuZFdORVJsDQpWMlpuUTNSV2RHUnViMU5CVVdSQlVFNVJhRkJLUnpocFpqUkdObEkyUkc1bGJtYzNWR1p3Y0ZOV1VGRlpTSE5MV1VOeGIwdExSQTBLDQpPVmM0ZDBSUE5uaG1NRGhxVXl0VGJqZFJTbU56TDA0dk5YTnZPR0ptY0hCclZHMTRPWGhuUld4NU5VcEphSGQ1Y21OSlIzQTNVaTlEDQpiRTQyRFFvd2FGYzVXWHA2UWpCelNHcEJaVTVyU1VGUVRFOU5VbWhYSzBGMldXNDFhRXgzVVhwaFpXTkJLMHBtYlhaSVkwcHpXbFpFDQpTbE5WYkd4VE5VVU5DazlFTm5CVWRVdEhiMHRWVUVGMVJHZHBia0Y1Y1hWdlJsSTRXbEZPU1ZGd1VVazVaVXQ1VTJKWE1YcFdlbU5NDQpXbVZzTVM5WGVFWjRWV2RYWlEwS1RuZEZNSGhSVVdsMkt5czRaMEZXY1VRMVVrSkVaRFpHZGpZNVJscENNVkJOYkhjMVFtcERhVGRwDQpRbnAzVlhaelozVXdWVzFuWkZFcloxb3pEUXBsU1RaWE1reFNUVlpZVVVORWQwdDNPVGN4WkRCTU1XcHhObVpFWW10TFRrbFlWSFZSDQpiRVk0YjJ4WGVWUm1kMjB6SzNCWWRtZGlkVkZDZEVrTkNtaGthVzluZFdFd2NFbEVRa2RRYkRsU2NtZEVORk5VY2pKblRVSkZNazh5DQpXV1ZKWkc1TlFWVldaRTVaZWxWU09VRnhZa3BuUW01TmRUVTJMdzBLT0hKa2NTOW9URnBSV0VweUwzaHpkVFZFZVVneGJrZDFaMUpMDQpVMFkyZERWa1NscFpTa1F2TjFoTk5FTktXa0o2T0hacldFTmpjVGR0VG1kU0RRcDNNV3NyTUdaWlZVSmplWE54V0ZGTmRUTk9XbG81DQpPVlUwTkhReFkyNVdjVzlLTm5vd2JsRkhTbUpMWkdoSWRpdEZRVE14VTFWcFYzbEpRbkFOQ214V09VVm5UbVZpTTBzNFRHOUtVRTlpDQpjR2RxVldzd1RqSnVTMEpoUlhsNFVEaFJaMkZEUTFwc2FqVjZaRFZZYkV0RlJFNUlkVU16UjJreVV3MEtia1F2WW10NFkyWnNUa2hzDQpXa1IyTUdJeFdFZE5jVWxDWkV4RWFsVlhUVVYyWlhkelIzVndkRlJCUTI1SWF6UjRTREI2TVdWUmNrTk5UM3BQRFFwaGJIa3JZV1k0DQphblJGTVZvNE5EQk1iekpSYjFVM00yVkVVamRhVkN0bFYyaE9RMWhZVkROU1oydzJWbVZTUldSdGVWcDJWRzFGVFhWUE1rSU5DbWx1DQpURzkxTVUxSmVXeG9jMWxQWkM5dlZ6Y3dVVzlPY1V0TVUzRXpSRzluT0VsTk0ycHFhR3BOTjBzMFpFWkJWRkYwY25WYVdDOUJWWEZSDQpTZzBLTkcwMFZEWlJjbEJFTDIxbE9GUjVTekJSVjFZMFV6UkVaR0poVlZWeFVWVmlVbmRLYTJoSU4wZDZWR3htU0RWSE5uUXljbkZtDQphV1J5Y3poakRRcDRia1Z0U1hadlZVYzNXa3AwVUVGdFdXOVVTbFJEWjI0ek9IYzJTV014VkVsTVRXWktSRmxRTVdaa1lqSTVTMjVsDQpaWHBPU0hJMVFrd3pSMDhOQ25kbWJUWjFhVE0zVkdaVE5tTXZhRkkyV0RFdmFsQjZkRFEwUjA5WWIzbDBOMFJuYUZndlZYRlljVEowDQpjREpqY2pGQlRVaFJXbEV3U1hGeFRBMEtkVXBFYWtWaFdIWnBPSEZ6WlN0dlJHZFRXaXR5YURSUU5Fc3JMMDF2UzFwaGRVWndPVXRyDQpZalpqZDBWRVUySnZVa3haU0ZGT1lrZE9MMjV4RFFwUVprSllOMGhETjFONmRYb05DajFXVUdkNURRb3RMUzB0TFVWT1JDQlFSMUFnDQpUVVZUVTBGSFJTMHRMUzB0RFFvPQ0KDQotLWIxX0FQdGpyQXJEM3h5dmVwZW5NR0lubTNseEpCM1lmSXZOQk04TXZqd1NrYzANCkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vb2N0ZXQtc3RyZWFtOyBuYW1lPSJwdWJsaWNrZXkgLSBzY2hsZW1hemxlQHByb3Rvbi5tZSAtIDB4QTUxNDAwMDUuYXNjLnBncCINCkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJhc2U2NA0KQ29udGVudC1EaXNwb3NpdGlvbjogYXR0YWNobWVudDsgZmlsZW5hbWU9InB1YmxpY2tleSAtIHNjaGxlbWF6bGVAcHJvdG9uLm1lIC0gMHhBNTE0MDAwNS5hc2MucGdwIg0KDQp3VjREZVdmZ0N0VnRkbm9TQVFkQTRrR3dURFNiMWZkNHYyTTZZQ1JjdVgyS0hOdkhkWkk1dWVkMW9rb2hYd1V3NUZhbG9USUhKSWlPDQp4N0ZFOCtKTThoYWk2S3NjYk5NTGZZV0dDUDJIOXh2K0NZUzlKNG52WjFPdXdKV0RWYlFKMHN1NEFhamhOZG1YOHRvNXRCZWNLdyt2DQp4U3NmQVNLQ2htNG1welVManhxUmIrK3Q1YVFYd0VORFMrNVl0ZVd3WHJlS0d5TVNXZlc0VjVDSVIzTnhydXp6ekZudU55anhrUHcvDQorRFpXMHJaeDgyMjVJZEE1bEdGNlBlWWg3NFhkcmw4QXE5QzJJeHBneFBUV3RhQXhwSEo2OVZmTUNtMnRUS09PZlU0ajRHLzE0bmw5DQprR3hNaVVKdUwrTlE2YzlZajNrVmxUZGM3UTVDeGFzN3N6aDRENHVZenNvSUhpb1dVc2J3cEpnWkl1cnNxc2tqME8rUTJvRTQ1eVNvDQorVlQwRXE4MnQyYmNFWm83b0gzQ2xiNStFZFpIUFdNWkhHY0pvaVJKeUdjVTc2cVlqQzh6V3JzcVNvdFp0UGxSVHRldWpJay9kODIxDQo3MnRrZDFzN2dqS1dIWi9jYjRQbTF0RVQzYWtQWFdyYmNudDFRMGh5SzMzdGpaOWk4cGVLZEFkT2N5M0E4VlM1RUJLYlA2MjA3NExuDQpjK2pYVHVSaml3TWk3aUJ2NnNWeElJNXdiRSs1OFBidDBKZzNXbm5JTmRXUGlha05jWUlyVzk0S0xqd2dXVW16a0dRTkJWNklTS20yDQpxS2RSN0twa0RCTjN2NU5SQ0ZQbWltdnFnVDFkZkFjb2w3b1ZIVk5JQU80aXlUdWlYN295TjcrR3lHa212TW92TERHem4vckQycjg4DQpqZkZLRUJYc1llNUdPVUNINHdJelMwRzVTektXQlRBNVByUW0yS2ljUmtkSWNlWUZrOWRoN0VFcnZGMjAxTXVLYysrU29rMWNOL0NFDQo4UFNTU0MxNHkyNVRxT3EwR3d2ZWhQcm5ta3lpOHJWd0thendMaEJxb1NheGVzNzhNZTk5SS9hVTdIdlkySFpBMS94U1ZkRnNHUnZqDQpLTjFROU4rd1dnYm5FNFJEWGNWcTZsNVdXNjFrUkx5QUhGay85L2I2TlNqcTY2MThkTXJJTTJwaWx1M0xCQjBKazJTaWM3ck1SdmZXDQo5YkZrNG5rQ2xJcjZoZHVkQkpQQmgvZFFVS2hKbnk4Z0NtamJ5YkxRLzRodzNJdjBiaHcwMERlNHV2RUpBZ0t5T1g1dUJrYzNiRTc4DQpxQmExUkh4akd4Y05pOEZiNWpnb3c0cnpMUW45eEVydnRFU013SDNwbUN3ZkVXMnRGbnlZdEZnazZsa2RwNnVRUjdUOWxMd2FqL1I4DQpGU045ZkZOMU4zb1kxWWpwelYyQ0ZNN2lpZk1DTGJvdHRzOG1ySnpKY1hnRTMvaVVRTFZLZ1VsbEFGMGV5NUREUHdqeGNiT01jbS85DQpxQUZxZmJia2I1Yzl4N0JGRmRZSmVKOHpBeVFvSk9WWnhCRC9yS1d0UzFpemZ1eTVmaGljb2tScE1JcTZQME5QVGNENlExUFdHOTM4DQo1MmlUbFpZRS9UT3FwL0c4VGM5SHBRRTVjUlFDaE1WelYvNHBIZXFxaFduOCswOUl2R3ZmcnE1RXcvenVxRlhVR3lqUXN6Qkh5UEN3DQpYQkhKOGRNaWZrWlpseXlHMkdpdXM5SVJNOFNudTBYVTZoZkUzV3ZSdTRLWkR2TllzM0o1N1JDdFVHVkk5aEtJMXlqZ0pXcGpDQzRTDQo4NTFKZ0paVUZaVnlTaVQ3dTRkY0pFNkNyNjZ6Y00yTlZERi8xakNRc01UalZyTXhWSEx1MmRkQ24zUVMxRFRaY0E4bnFDWWNhZlZQDQp3a2xvcG5NL21KTjVMM3ordVRRSWxmYzlEbkIwelRlQlUrajdqR3VtQXRvTWZOa3ZzZDI0LzNINSswZDhldnpibWhScUNtSW91eGdmDQpTWXlxRVNSdzI5RDZCRmRXMUwrYTlBak1GT05lQ2h0S05vUEErZkpBVUJWOFRuZVZhcGtpYW00alBmelV5TnQ1OTJEOFJJdjJieVpqDQoxYjJmVnNtcm9leVBUYWJ3cEhkek5kTFY5OVdNQ2NCaVNpRm5ZYS9qSC80OTl6enk0UXVyMkZJQ3pWVk9ab2tzSjJHZGlZL1FmU2J0DQprMkM5L1VLMFNSZ1YzeW1xb0drblBrNlFSUWl6SDVCbzFpcURwZWxZNjRKM1hvSHVscjhCUHg4OWhEcE11c1M5SUplcU54MVdhY2RQDQpnYUpXb01ZMWdoRkhNWkJXUk5XeE0xRGhzd2pCWmpMd1AwaCtGdWY4bm9sVVJ2L29RQUFqYUJYMnMxblVLQVVkZUdoc1ZnbjBMY05qDQpKdE5pTm8rVVVPbGNyS3dCbzNURUt3S0RIeUV0dy84MXp2dGRzZXl5SmdtRG81Sy9VY2lybWNzTGlPellPT3NMemw2ZDlMUS9wWEl4DQprdjVOVXRVVWxxTDJJN2t3NFVOVUNUNTZNczFDZDI1S0R5OGNWYjdoRkhReEp2a3h2WVlob3h1ckZEZlBwbEkzc0sxSVZOYXNTc0paDQp0ZitxYUdJNi9nZXdUV2RSRzZTb1ZtOVk4d3Q3MVRtLzgzZElSSFdEZFhIRi83NXE0YmVrYlE5NG82bTdLVFl0Tk4wVUxuUFpMazFODQpOeTk3MVNiZ2YxQTI4K3ZQSFJYZGdHK3E4elQ3c29UNnBPK0xXRWFaQ0hCc3ZReTN1MzBlVFdHOXdlSmF1d3Q0TEVQTlNIMit1RVpkDQpENW50ZUxOVXVqUjl6aFFwMHY2ZG81aDY5K1JJbFVLY1FHV25oL3JJczhRTUxOeHExNjRacUJDM2gzMGErZ1ZqaHhVVnlyejYrRVRSDQoyamR0NGVJRVo5cCtUcWttWSt1c3FsdmJJS2FIRWF2T3kzS3BSM2NGVU8yaE9IQTUrb2FxR1dMVUpSU3RLS1NiY3UxY0tEQVZMaVNhDQprUzFlZ0F0QXNiemNMdW5FS3BQNjkrRXB5WGV4bG54MG1QZnp6MXhGdDJoeTVrWS9GUVhqU3ZKc3hVbHNDcVV5NlF0VGF6OXJLeDBVDQpsaTlSUmZzWnpZaEtRUmpjVmlIUlZQdW5hKzhGbElyTitKNXFadGFHOEhFYm9GTmtPcG1XVkYwWG5IK1pLbHBwQTVoSS9oRHI3YU8wDQpGRko3cU9TWExjQjZUcmxNSU15b01oT05xZm5FZmt5SWU4SjZSaTcxWXQycWM3S2pwTWo1cmpyVmtOK1RVV1lPbjY1WUZUL2U0Rno1DQpxVmpzTmgrV3JqY0U1cWRydlpaT2ltY2ovRm1RbVdNVmdXMzlyY3NjZ0haUlM1RWhwNGFrMGdBcVloeWhvVisvVDNBRHdTVm9jNUszDQpPeEo2ZExUOVdISTZMOXRaNjBYZEZJeWhXbnI0K0JTM3JQOW5TVU51UGtWY3gyUWZzbEhTRVNYK0dCczBzUWg1WWsvcHhxMnpnVzgzDQpaUWpSamd2cDB4NERCa3A4cDJTbk9TZEU5ZUhhMkxacjFuWmlWcExNSTNBTER4aFhkOVNWVU9WeGNBUzdBL3k5N09NSFk5QUE0Rms3DQp6RHhRQVV5QVJVUEdnbVVjQ0xyRUdTeXlHWnpUSUp6K21jZDJaSzJSZVljb3Mrbkh4UE8rRUZWZGRWUDFxa1htTUhSeXRCbmpXam5nDQpLWlBGZ0NZM2EyVTY0UGljSnh5bWpxL2UralZoN3VNb0tqY3BWUXhDeFpOd2R3SHpOZjM3TlhFUmJlUWtWNWhQUGMrSUgzWUZHbHhvDQp3WFBMVFpDeDZ0Q1hIcmQ5dXZqR1Rncjl5clRpNjVJOWNJZnZZSFczMXlLdG9yNjJJcWdZV3ZycmNrb2lGR05ndTFMOTkrYXVEekp3DQp4WlNTa3NCWm5HQ1Q3VllmaG1naUdYNjFqMXgzQ0JpSHExdjUzWXRNWDlueCtZWGtGWEhqY0ovclpoSk95ZjR3YUtPbnFTbXlXTFlVDQpqYlFBZXp1Wk92VUxid1Bab0hOV0szNmZaV2V6b0VYUDhQejJHd2FoWjJVZFBaSHRDaFFCd01ZRUh2MWxubDRFSm15aHVsOUc2S1c0DQpXN2dmb3JJNHlWSHY4enp5NFgwZEdiMCttd1lnVXZTOEJ1UHlpYWEza1BoNE1UUi9tQkd1QmlYRG1RZXU3bE56NGVMaFh5Mm8rekRlDQpKeXRTbXRrQk0rRC9YYnpsRVVlV01vdXhwbVFONTZjeXNKU1RjZW5MUFd3cFlRUW9Pd1EvWmZ1cVVTbzRkOWFkRlBUS1NDaVNOTElUDQpCVi81a1NjMkdvNlRrVlozL0FiTm5pd2Nqczh3eGpweDJiN1ErR0xMcVNNRFRGeU4vWTc2eUJCMDBzWDl4MHE3d1c2YWtaOGZUVFNFDQpLYXhDeUxWY2gxeDFVVUQ0TFpNd1VXUWNXUXphTVU1YkFYSm5ac25YTlBaSnNaNlh6ZjJWNWcyOG4zVFMyRy9WQ3lzV1ZObjkzVnpjDQpPWTJiVm9KVXdQSklpVC9IME5mQzVISWQ0UXNTZU9LZzMxN0R6T1lySFRKczhjeU1PMEUxeU0rOVhoQWZ6aGxVRndpLzZZTVAxVXVWDQo3a0NrSTFyWjJBTVIzSS82L1FBVDlZUDByblJsQXl2TGpScG5FVnREVEx1S0RXaEpZWThBQ25oMi9qVGFuclUwTC9TaEkycGJrYzZMDQo4NHdqSFpQekNVc0UwNXg1ajlnRXhEaXdHVnFPWjVGa0l1WTVqaW1YVVR0eUNXN1lLcnVGdGtUd0h5eGxBSEZZNGdlay9GL2xxY20xDQp2Y21oaTNMMnJRYjZwSEl3d2tScFNvTVNFQmlSc1JianJrdndGSm5uS05GNFZiYm5RUVdlVkxIVlJSM3ZWYzZ1V2FMVUxjYVVYU3BSDQp2SUw0UzlJVUdlMmxWT3ZlUWVCREY0ZXhXMjdCaVVYU0ZCeXhIdkZGSDhaM3RnVGd0ZTRXaTBYUGkyWko0ZmxVQmxTbkpacmRnM0Y5DQpZOTIvSGcySHpCbU9QWmxXVFhURlpSNTRjT0lqTmpGK2hFcjh6dWxoYTF3RnNQejF5Z09CbXA2RU5XeHhoQk5vMUsrQ3AyTzZJazcyDQpITEhMQWo2TDhUNmhldVg2VG1yMm1nUWw4N0VncEdHRVYxRU9iYkl0RWgveENDa0Fqb0RhSlBnZkRPRk5jSWhnSTYwOEZvV0pYOThkDQpBeDRnUkxveFhodFlILzhyVjFvNUw4ZWNoNzVVbmhJb3VubS9LUGE1NUtqaWpDSVY0cSs1ZG96VnJzOXk5eDRwMnRCMHRkSytIbDhSDQpKbGdwb1gzSEhmWmUyM0ttck5nQXZoTU9oMjUvcEUvV2M0QUQwQ3hCbjR2VmxKQ1lJTTV3S0R0bEVyM1loaEx6Q3RZbzRiSUpWKzNKDQorRmV4UDJUaTh6MldlejI1Mk5uaXZkWDU2b2hoT2xKYktkSkZ0MEJQczAvWGZPS2s2OXZaNHhMRi90SlVOa3YzdnovUjhURENOYWlWDQp2dlJja1ZxZFROOENlbXFuKzVZaEJ2ZE44VjFTc2xYK3RwdTJHOTUwSEFKM1dBSS9KNEI3Mk05KzRtYUFZQzF4Y1J2bHYrVlpPcjMwDQpHRGYvbzdaZCtrbXppdytuWE1JM0RKU1VoWEhRQWVaeWp2YlFENVBTTXREOEo2aE5TUzl6c1NRZFhvcm9oV2FKVlNaTVpxQWZINEZXDQpXMXRNREtkYmF4OForM2pzQkp3TlQyMXRvY1JrVGo3ZDVJbU9DbXU5TnpvWHFMM3QxUDZaOUI5NA0KDQotLWIxX0FQdGpyQXJEM3h5dmVwZW5NR0lubTNseEpCM1lmSXZOQk04TXZqd1NrYzANCkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vcGdwLXNpZ25hdHVyZTsgbmFtZT0icHVibGlja2V5IC0gc2NobGVtYXpsZUBwcm90b24ubWUgLSAweEE1MTQwMDA1LmFzYy5zaWciDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQNCkNvbnRlbnQtRGlzcG9zaXRpb246IGF0dGFjaG1lbnQ7IGZpbGVuYW1lPSJwdWJsaWNrZXkgLSBzY2hsZW1hemxlQHByb3Rvbi5tZSAtIDB4QTUxNDAwMDUuYXNjLnNpZyINCg0Kd3NGekJBQUJDQUFuQlFKai9ITEVDUkJoYlZscndnWmRTQlloQktVVUFBVlhVclJQdWVvSWxXRnRXV3ZDQmwxSUFBQmVpdy8vWnpvbg0KSWVwTXdEbEVZRTRMazBwVXp0NEpWSERGVldxNE93eUdnK2pTREsxRVYwQzVxQVMrVVhIWGMxMHdLaHVqNDNWRTdsb0FiQ1RiclZ4UA0KMDZqN3BuenZTbTNjTXM5VVN0Z3FBWWY5RDZkUUZVTGx6NEtTd0owTXZJVGhQKzNGa2ZOVVVlOGF6aU9JdWUvbWVBL2FqQzk4ZjRySw0KYklDNDhFZTVUNjZrclNPaDZnR0JYUk1oc1dvakpTSEVVUEkxeUdEN05EQnIzeEt0ZHZ5Q3VmUm9JV01YT2ZFRDNuU1FNWkpqSkZoSg0KbEN4SS8zVHRtU3VtVlJoYXhVOFdqVjNjZUNPY292SGhJMllaMktBTDNvT0FnY24rQVE3OU5rV2VDV0hucU9VSlprMVpDSWk1ZXhTcg0KOUp2MU5QNnRiMm56THdQTkR3ZjRPU2NMWFgzS0JaVGxyMnVxU3RXUFExR2pqNW92UCtnUHpvVWNoQVRYTTg5YkdZWlZTbkpkbVRMMw0KZ1lQbW51WEM2R0VyNUVKSzVrMEtjM2JWTi8veXNkeDBhSEVJbVkrMEx5b1ZUSS8yNFVMT1FtL29VWC9MdHR5NkNnSWo0MEJNMnZyOA0KenZKMm9JZVlFWndHYnN6Y1FpcDBuako5dWNHRGNoVWpwcEVCamQ3S2dsWWtBMzBlK1V5TGZlQmdnRXdHVnRFTFJ4RHdJWjZHOTUzTA0KUUF1L2ZSZXprUS9JbGJEakQwbWF2WnY3dHZadW5WL1JHZWxDbUJSajZNSDRxakJPUmVJUnVXQmxSd29hb1AwdWdiL01hNm9NT3paaQ0KdzViQXFSemxjSEc5NWZRTnREV0ZHNTBYU0s3MFMvK0ZxazdxOVJhSzNQS0owQmM1UVJNZHIxeHJwTFA1anUwVjJzYVV1T2s9DQoNCi0tYjFfQVB0anJBckQzeHl2ZXBlbk1HSW5tM2x4SkIzWWZJdk5CTThNdmp3U2tjMC0tDQoNCg==", + "historyId": "2491472", + "internalDate": "1677488866000" + } +} diff --git a/test/source/mock/google/google-data.ts b/test/source/mock/google/google-data.ts index 9f93a85039d..706f5045f14 100644 --- a/test/source/mock/google/google-data.ts +++ b/test/source/mock/google/google-data.ts @@ -6,6 +6,7 @@ import { readdir, readFile } from 'fs'; import { Util } from '../../util/index'; import { ParseMsgResult } from '../../util/parse'; import { Buf } from '../../core/buf'; +import { Xss } from '../../platform/xss'; type GmailMsg$header = { name: string; value: string }; type GmailMsg$payload$body = { attachmentId?: string; size: number; data?: string }; @@ -217,18 +218,43 @@ export class GoogleData { public static getMockGmailPage = async (acct: string, msgId?: string) => { let msgBlock = ''; + let attachmentsBlock = ''; if (msgId) { /* eslint-disable @typescript-eslint/no-non-null-assertion */ const payload = (await GoogleData.withInitializedData(acct)).getMessage(msgId)!.payload!; const fromHeader = payload.headers!.find(header => header.name === 'From')!; const fromAddress = fromHeader.value!; - const htmlPart = payload.parts!.find(part => part.mimeType === 'text/html')!; - const htmlData = Buf.fromBase64Str(htmlPart.body!.data!).toUtfStr(); + let htmlData: string; + const htmlPart = payload.parts!.find(part => part.mimeType === 'text/html'); + if (htmlPart) { + htmlData = Buf.fromBase64Str(htmlPart.body!.data!).toUtfStr(); + } else { + const textPart = payload.parts!.find(part => part.mimeType === 'text/plain')!; + const textData = Buf.fromBase64Str(textPart.body!.data!).toUtfStr(); + htmlData = Xss.escape(textData); + } + const otherParts = payload.parts!.filter(part => !['text/plain', 'text/html'].includes(part.mimeType!)); + if (otherParts.length) { + attachmentsBlock = + `
${otherParts.length} Attachments
+
` + + otherParts + .map( + part => ` +
+ ${Xss.escape(part.filename!)} +
+
` + ) + .join('') + + '
'; + } /* eslint-enable @typescript-eslint/no-non-null-assertion */ msgBlock = `
Mock Sender
${htmlData}
+ ${attachmentsBlock}
`; diff --git a/test/source/tests/decrypt.ts b/test/source/tests/decrypt.ts index af50d6fc59f..a7b829715bd 100644 --- a/test/source/tests/decrypt.ts +++ b/test/source/tests/decrypt.ts @@ -892,6 +892,91 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw== }) ); + test( + 'decrypt - protonmail - PGP/inline signed and encrypted message with pubkey - pubkey signature is ignored', + testWithBrowser('ci.tests.gmail', async (t, browser) => { + const acctEmail = 'ci.tests.gmail@flowcrypt.test'; + const dbPage = await browser.newExtensionPage(t, 'chrome/dev/ci_unit_test.htm'); // todo: url? + // add the pubkey of the sender + await dbPage.page.evaluate( + async (pubkey: string) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const key = await (window as any).KeyUtil.parse(pubkey); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (window as any).ContactStore.update(undefined, 'schlemazle@proton.me', { pubkey: key }); + }, + `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +dUDdlDNGYaMN +=efCn +-----END PGP PUBLIC KEY BLOCK----- +` + ); + const accessToken = await BrowserRecipe.getGoogleAccessToken(dbPage, acctEmail); + await dbPage.close(); + const extraAuthHeaders = { Authorization: `Bearer ${accessToken}` }; // eslint-disable-line @typescript-eslint/naming-convention + const gmailPage = await browser.newPage(t, `${t.urls?.mockGmailUrl()}/1869220e0c8f16dd`, undefined, extraAuthHeaders); + await gmailPage.waitAll('iframe'); + const pgpBlock = await gmailPage.getFrame(['pgp_block.htm']); + await BrowserRecipe.pgpBlockCheck(t, pgpBlock, { + content: ['Sent with Proton Mail secure email.'], + encryption: 'encrypted', + signature: 'signed', + }); + await gmailPage.close(); + }) + ); + test( 'signature - cleartext signed messages from HTML are re-fetched when needed', testWithBrowser('ci.tests.gmail', async (t, browser) => { From cf3c5fd4e3fddfb774e0e7699ad0059df54f6f1c Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 3 Mar 2023 10:01:01 +0000 Subject: [PATCH 2/5] Added test for inbox-active-thread --- test/source/browser/browser-handle.ts | 4 +- test/source/browser/test-urls.ts | 5 +- test/source/tests/decrypt.ts | 91 ++++++++------------------- test/source/tests/tooling/consts.ts | 56 +++++++++++++++++ 4 files changed, 88 insertions(+), 68 deletions(-) diff --git a/test/source/browser/browser-handle.ts b/test/source/browser/browser-handle.ts index 86c84d6dcd2..39f93398f96 100644 --- a/test/source/browser/browser-handle.ts +++ b/test/source/browser/browser-handle.ts @@ -49,8 +49,8 @@ export class BrowserHandle { return this.newPage(t, t.urls?.extension(url)); }; - public newExtensionInboxPage = async (t: AvaContext, acctEmail: string): Promise => { - return this.newPage(t, t.urls?.extensionInbox(acctEmail)); + public newExtensionInboxPage = async (t: AvaContext, acctEmail: string, threadId?: string): Promise => { + return this.newPage(t, t.urls?.extensionInbox(acctEmail, threadId)); }; public newExtensionSettingsPage = async (t: AvaContext, acctEmail?: string | undefined): Promise => { diff --git a/test/source/browser/test-urls.ts b/test/source/browser/test-urls.ts index ff16e851031..14e235cee5a 100644 --- a/test/source/browser/test-urls.ts +++ b/test/source/browser/test-urls.ts @@ -21,8 +21,9 @@ export class TestUrls { return this.extension(`chrome/settings/index.htm?account_email=${acctEmail || ''}`); }; - public extensionInbox = (acctEmail: string) => { - return this.extension(`chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}`); + public extensionInbox = (acctEmail: string, threadId?: string) => { + const url = this.extension(`chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}`); + return threadId ? url + `&threadId=${threadId}` : url; }; public mockGmailUrl = () => `https://gmail.localhost:${this.port}/gmail`; diff --git a/test/source/tests/decrypt.ts b/test/source/tests/decrypt.ts index a7b829715bd..9b38626e779 100644 --- a/test/source/tests/decrypt.ts +++ b/test/source/tests/decrypt.ts @@ -898,70 +898,12 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw== const acctEmail = 'ci.tests.gmail@flowcrypt.test'; const dbPage = await browser.newExtensionPage(t, 'chrome/dev/ci_unit_test.htm'); // todo: url? // add the pubkey of the sender - await dbPage.page.evaluate( - async (pubkey: string) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const key = await (window as any).KeyUtil.parse(pubkey); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - await (window as any).ContactStore.update(undefined, 'schlemazle@proton.me', { pubkey: key }); - }, - `-----BEGIN PGP PUBLIC KEY BLOCK----- - -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 -dUDdlDNGYaMN -=efCn ------END PGP PUBLIC KEY BLOCK----- -` - ); + await dbPage.page.evaluate(async (pubkey: string) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const key = await (window as any).KeyUtil.parse(pubkey); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (window as any).ContactStore.update(undefined, 'schlemazle@proton.me', { pubkey: key }); + }, testConstants.protonPubkey); const accessToken = await BrowserRecipe.getGoogleAccessToken(dbPage, acctEmail); await dbPage.close(); const extraAuthHeaders = { Authorization: `Bearer ${accessToken}` }; // eslint-disable-line @typescript-eslint/naming-convention @@ -977,6 +919,27 @@ dUDdlDNGYaMN }) ); + test( + 'decrypt - protonmail - PGP/inline signed and encrypted message with pubkey - pubkey signature is ignored - inbox', + testWithBrowser('ci.tests.gmail', async (t, browser) => { + const acctEmail = 'ci.tests.gmail@flowcrypt.test'; + const threadId = '1869220e0c8f16dd'; + const inboxPage = await browser.newExtensionInboxPage(t, acctEmail, threadId); + await inboxPage.waitAll('iframe'); + expect((await inboxPage.getFramesUrls(['pgp_block.htm'])).length).to.equal(1); + expect((await inboxPage.getFramesUrls(['pgp_pubkey.htm'])).length).to.equal(1); + expect((await inboxPage.getFramesUrls(['attachment.htm'])).length).to.equal(0); // invisible + const pgpBlock = await inboxPage.getFrame(['pgp_block.htm']); + await BrowserRecipe.pgpBlockCheck(t, pgpBlock, { + content: ['Sent with Proton Mail secure email.'], + encryption: 'encrypted', + signature: 'could not verify signature: missing pubkey 616D596BC2065D48', + }); + await inboxPage.close(); + t.pass(); + }) + ); + test( 'signature - cleartext signed messages from HTML are re-fetched when needed', testWithBrowser('ci.tests.gmail', async (t, browser) => { diff --git a/test/source/tests/tooling/consts.ts b/test/source/tests/tooling/consts.ts index 10f1bfb6a32..0a2a17710f9 100644 --- a/test/source/tests/tooling/consts.ts +++ b/test/source/tests/tooling/consts.ts @@ -61,6 +61,62 @@ EexnUn1E1mOjFwiYOZavCLvJRtazGCreO0FkWtrrtoa+5F2fbKUIVNGg44fG 7aGdFze6mNyI/fMU =D34s -----END PGP PUBLIC KEY BLOCK-----`, + protonPubkey: `-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +dUDdlDNGYaMN +=efCn +-----END PGP PUBLIC KEY BLOCK----- +`, expiredPub: `-----BEGIN PGP PUBLIC KEY BLOCK----- mQGNBF04cLABDADGVUmV8RtjsCIrmg97eO9vmxfc6FeH1cIguCXoFpQxCSk0/Hv8 From f7e2e5e1decca743f20a2d5a593153de574f3dce Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 3 Mar 2023 12:13:54 +0000 Subject: [PATCH 3/5] refactor signature detection --- extension/chrome/elements/attachment.ts | 15 ++++++----- .../pgp-block-attachmens-module.ts | 5 ++-- .../pgp-block-decrypt-module.ts | 20 +++++++------- extension/js/common/core/attachment.ts | 26 +++++++++---------- extension/js/common/core/common.ts | 3 +++ extension/js/common/core/mime.ts | 19 +++++++------- 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/extension/chrome/elements/attachment.ts b/extension/chrome/elements/attachment.ts index 971b3df6c8c..8ba53e5eedc 100644 --- a/extension/chrome/elements/attachment.ts +++ b/extension/chrome/elements/attachment.ts @@ -4,7 +4,7 @@ import { Bm, BrowserMsg } from '../../js/common/browser/browser-msg.js'; import { DecryptErrTypes, MsgUtil } from '../../js/common/core/crypto/pgp/msg-util.js'; -import { PromiseCancellation, Url } from '../../js/common/core/common.js'; +import { PromiseCancellation, Str, Url } from '../../js/common/core/common.js'; import { Api } from '../../js/common/api/shared/api.js'; import { ApiErr } from '../../js/common/api/shared/api-error.js'; import { Assert } from '../../js/common/assert.js'; @@ -66,7 +66,7 @@ export class AttachmentDownloadView extends View { this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail'); this.parentTabId = Assert.urlParamRequire.string(uncheckedUrlParams, 'parentTabId'); this.frameId = Assert.urlParamRequire.string(uncheckedUrlParams, 'frameId'); - this.origNameBasedOnFilename = uncheckedUrlParams.name ? String(uncheckedUrlParams.name).replace(/\.(pgp|gpg)$/gi, '') : 'noname'; + this.origNameBasedOnFilename = uncheckedUrlParams.name ? Str.stripPgpOrGpgExtensionIfPresent(String(uncheckedUrlParams.name)) : 'noname'; this.isEncrypted = uncheckedUrlParams.isEncrypted === true; this.errorDetailsOpened = uncheckedUrlParams.errorDetailsOpened === true; this.size = uncheckedUrlParams.size ? parseInt(String(uncheckedUrlParams.size)) : undefined; @@ -238,17 +238,18 @@ export class AttachmentDownloadView extends View { }; private processAsPublicKeyAndHideAttachmentIfAppropriate = async () => { - // todo: better rely on the preset this.treatAsValue - if (this.attachment.msgId && this.attachment.id && this.attachment.treatAs([]) === 'publicKey') { + // todo: we should call this detection in the main `core/Attachment.treatAs` (e.g. in the context of GmailElementReplacer and InboxActiveThreadModule) + // should be possible after #4906 is done + if (((this.attachment.msgId && this.attachment.id) || this.attachment.url) && this.attachment.isPublicKey()) { // this is encrypted public key - download && decrypt & parse & render - const { data } = await this.gmail.attachmentGet(this.attachment.msgId, this.attachment.id); + await this.downloadDataIfNeeded(); const decrRes = await MsgUtil.decryptMessage({ kisWithPp: await KeyStore.getAllWithOptionalPassPhrase(this.acctEmail), - encryptedData: data, + encryptedData: this.attachment.getData(), verificationPubs: [], // no need to worry about the public key signature, as public key exchange is inherently unsafe }); if (decrRes.success && decrRes.content) { - const openpgpType = await MsgUtil.type({ data: decrRes.content }); + const openpgpType = MsgUtil.type({ data: decrRes.content }); if (openpgpType && openpgpType.type === 'publicKey' && openpgpType.armored) { // 'openpgpType.armored': could potentially process unarmored pubkey files, maybe later BrowserMsg.send.renderPublicKeys(this.parentTabId, { diff --git a/extension/chrome/elements/pgp_block_modules/pgp-block-attachmens-module.ts b/extension/chrome/elements/pgp_block_modules/pgp-block-attachmens-module.ts index e0b0b37a173..b8340bd3d39 100644 --- a/extension/chrome/elements/pgp_block_modules/pgp-block-attachmens-module.ts +++ b/extension/chrome/elements/pgp_block_modules/pgp-block-attachmens-module.ts @@ -11,6 +11,7 @@ import { Ui } from '../../../js/common/browser/ui.js'; import { Xss } from '../../../js/common/platform/xss.js'; import { KeyStore } from '../../../js/common/platform/store/key-store.js'; import { XssSafeFactory } from '../../../js/common/xss-safe-factory.js'; +import { Str } from '../../../js/common/core/common.js'; declare const filesize: { filesize: Function }; // eslint-disable-line @typescript-eslint/ban-types @@ -23,7 +24,7 @@ export class PgpBlockViewAttachmentsModule { Xss.sanitizeAppend('#pgp_block', '
'); this.includedAttachments = attachments; for (const i of attachments.keys()) { - const name = (attachments[i].name ? attachments[i].name : 'noname').replace(/\.(pgp|gpg)$/, ''); + const name = attachments[i].name ? Str.stripPgpOrGpgExtensionIfPresent(attachments[i].name) : 'noname'; const nameVisible = name.length > 100 ? name.slice(0, 100) + '…' : name; const size = filesize.filesize(attachments[i].length); const htmlContent = `${Xss.escape(nameVisible)}   ${size}`; @@ -90,7 +91,7 @@ export class PgpBlockViewAttachmentsModule { }); if (decrypted.success) { const attachment = new Attachment({ - name: encrypted.name.replace(/\.(pgp|gpg)$/, ''), + name: Str.stripPgpOrGpgExtensionIfPresent(encrypted.name), type: encrypted.type, data: decrypted.content, }); diff --git a/extension/chrome/elements/pgp_block_modules/pgp-block-decrypt-module.ts b/extension/chrome/elements/pgp_block_modules/pgp-block-decrypt-module.ts index 70239851ec5..6c60dae59e7 100644 --- a/extension/chrome/elements/pgp_block_modules/pgp-block-decrypt-module.ts +++ b/extension/chrome/elements/pgp_block_modules/pgp-block-decrypt-module.ts @@ -30,16 +30,18 @@ export class PgpBlockViewDecryptModule { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const mimeMsg = Buf.fromBase64UrlStr(raw!); // used 'raw' above const parsed = await Mime.decode(mimeMsg); - if (parsed && typeof parsed.rawSignedContent === 'string' && parsed.signature) { - this.view.signature.parsedSignature = parsed.signature; - await this.decryptAndRender(Buf.fromUtfStr(parsed.rawSignedContent), verificationPubs); - } else { - await this.view.errorModule.renderErr( - 'Error: could not properly parse signed message', - parsed.rawSignedContent || parsed.text || parsed.html || mimeMsg.toUtfStr(), - 'parse error' - ); + if (parsed && typeof parsed.rawSignedContent === 'string') { + const signatureAttachment = parsed.attachments.find(a => a.treatAs(parsed.attachments) === 'signature'); // todo: more than one signature candidate? + if (signatureAttachment) { + this.view.signature.parsedSignature = signatureAttachment.getData().toUtfStr(); + return await this.decryptAndRender(Buf.fromUtfStr(parsed.rawSignedContent), verificationPubs); + } } + await this.view.errorModule.renderErr( + 'Error: could not properly parse signed message', + parsed.rawSignedContent || parsed.text || parsed.html || mimeMsg.toUtfStr(), + 'parse error' + ); } else if (this.view.encryptedMsgUrlParam && !forcePullMsgFromApi) { // ascii armored message supplied this.view.renderModule.renderText(this.view.signature ? 'Verifying...' : 'Decrypting...'); diff --git a/extension/js/common/core/attachment.ts b/extension/js/common/core/attachment.ts index 10aa499bbd1..7f3d37ca96d 100644 --- a/extension/js/common/core/attachment.ts +++ b/extension/js/common/core/attachment.ts @@ -101,6 +101,15 @@ export class Attachment { return `f_${Str.sloppyRandom(30)}@flowcrypt`; }; + public isPublicKey = (): boolean => { + return ( + this.type === 'application/pgp-keys' || + /^(0|0x)?[A-F0-9]{8}([A-F0-9]{8})?.*\.asc$/g.test(this.name) || // name starts with a key id + (this.name.toLowerCase().includes('public') && /[A-F0-9]{8}.*\.asc$/g.test(this.name)) || // name contains the word "public", any key id and ends with .asc + (/\.asc$/.test(this.name) && this.hasData() && Buf.with(this.getData().subarray(0, 100)).toUtfStr().includes('-----BEGIN PGP PUBLIC KEY BLOCK-----')) + ); + }; + public hasData = () => { return this.bytes instanceof Uint8Array; }; @@ -154,22 +163,11 @@ export class Attachment { } else if (this.name.match(/(\.pgp$)|(\.gpg$)|(\.[a-zA-Z0-9]{3,4}\.asc$)/g)) { // ends with one of .gpg, .pgp, .???.asc, .????.asc return 'encryptedFile'; + // todo: after #4906 is done we should "decrypt" the encryptedFile here to see if it's a binary 'publicKey' (as in message 1869220e0c8f16dd) + } else if (this.isPublicKey()) { + return 'publicKey'; } else if (this.name.match(/(cryptup|flowcrypt)-backup-[a-z0-9]+\.(key|asc)$/g)) { return 'privateKey'; - } else if (this.type === 'application/pgp-keys') { - return 'publicKey'; - } else if (this.name.match(/^(0|0x)?[A-F0-9]{8}([A-F0-9]{8})?.*\.asc$/g)) { - // name starts with a key id - return 'publicKey'; - } else if (this.name.toLowerCase().includes('public') && this.name.match(/[A-F0-9]{8}.*\.asc$/g)) { - // name contains the word "public", any key id and ends with .asc - return 'publicKey'; - } else if ( - this.name.match(/\.asc$/) && - this.hasData() && - Buf.with(this.getData().subarray(0, 100)).toUtfStr().includes('-----BEGIN PGP PUBLIC KEY BLOCK-----') - ) { - return 'publicKey'; } else if (this.name.match(/\.asc$/) && this.length < 100000 && !this.inline) { return 'encryptedMsg'; } else { diff --git a/extension/js/common/core/common.ts b/extension/js/common/core/common.ts index 52e36574b66..e73a34d960f 100644 --- a/extension/js/common/core/common.ts +++ b/extension/js/common/core/common.ts @@ -201,6 +201,9 @@ export class Str { return filename.replace(/\.[^/.]+$/, ''); }; + public static stripPgpOrGpgExtensionIfPresent = (filename: string) => { + return filename.replace(/\.(pgp|gpg)$/i, ''); + }; private static formatEmailWithOptionalNameEx = ({ email, name }: EmailParts, forceBrackets?: boolean): string => { if (name) { return `${Str.rmSpecialCharsKeepUtf(name, 'ALLOW-SOME')} <${email}>`; diff --git a/extension/js/common/core/mime.ts b/extension/js/common/core/mime.ts index c2736bf1cab..40be1fe4bf4 100644 --- a/extension/js/common/core/mime.ts +++ b/extension/js/common/core/mime.ts @@ -25,7 +25,6 @@ type MimeContentHeader = string | AddressHeader[]; export type MimeContent = { headers: Dict; attachments: Attachment[]; - signature?: string; rawSignedContent?: string; subject?: string; html?: string; @@ -73,6 +72,7 @@ export class Mime { } else if (decoded.html) { blocks.push(MsgBlock.fromContent('plainHtml', decoded.html)); } + const signatureAttachments: Attachment[] = []; for (const file of decoded.attachments) { const isBodyEmpty = decoded.text === '' || decoded.text === '\n'; const treatAs = file.treatAs(decoded.attachments, isBodyEmpty); @@ -82,7 +82,7 @@ export class Mime { blocks.push(MsgBlock.fromContent('encryptedMsg', armored)); } } else if (treatAs === 'signature') { - decoded.signature = decoded.signature || file.getData().toUtfStr(); + signatureAttachments.push(file); } else if (treatAs === 'publicKey') { blocks.push(...MsgBlockParser.detectBlocks(file.getData().toUtfStr()).blocks); } else if (treatAs === 'privateKey') { @@ -109,19 +109,21 @@ export class Mime { ); } } - if (decoded.signature) { + if (signatureAttachments.length) { + // todo: if multiple signatures, figure out which fits what + const signature = signatureAttachments[0].getData().toUtfStr(); for (const block of blocks) { if (block.type === 'plainText') { block.type = 'signedText'; - block.signature = decoded.signature; + block.signature = signature; } else if (block.type === 'plainHtml') { block.type = 'signedHtml'; - block.signature = decoded.signature; + block.signature = signature; } } if (!blocks.find(block => ['plainText', 'plainHtml', 'signedMsg', 'signedHtml', 'signedText'].includes(block.type))) { // signed an empty message - blocks.push(new MsgBlock('signedMsg', '', true, decoded.signature)); + blocks.push(new MsgBlock('signedMsg', '', true, signature)); } } return { @@ -182,7 +184,6 @@ export class Mime { subject: undefined, text: undefined, html: undefined, - signature: undefined, from: undefined, to: [], cc: [], @@ -210,9 +211,7 @@ export class Mime { } for (const node of Object.values(leafNodes)) { const nodeType = Mime.getNodeType(node); - if (nodeType === 'application/pgp-signature') { - mimeContent.signature = node.rawContent; - } else if (nodeType === 'text/html' && !Mime.getNodeFilename(node)) { + if (nodeType === 'text/html' && !Mime.getNodeFilename(node)) { // html content may be broken up into smaller pieces by attachments in between // AppleMail does this with inline attachments mimeContent.html = (mimeContent.html || '') + Mime.getNodeContentAsUtfStr(node); From 54cd0730e245e910e69d22b2e211f637ad14664c Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 3 Mar 2023 12:39:46 +0000 Subject: [PATCH 4/5] fix treatAsValue --- extension/js/common/core/attachment.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extension/js/common/core/attachment.ts b/extension/js/common/core/attachment.ts index 7f3d37ca96d..8de49af3dee 100644 --- a/extension/js/common/core/attachment.ts +++ b/extension/js/common/core/attachment.ts @@ -41,7 +41,7 @@ export class Attachment { public contentTransferEncoding?: ContentTransferEncoding; private bytes: Uint8Array | undefined; - private treatAsValue: Attachment$treatAs | undefined; + private treatAsValue: Attachment$treatAs | undefined; // this field is to disable on-the-fly detection by this.treatAs() public constructor({ data, type, name, length, url, inline, id, msgId, treatAs, cid, contentDescription, contentTransferEncoding }: AttachmentMeta) { if (typeof data === 'undefined' && typeof url === 'undefined' && typeof id === 'undefined') { @@ -102,6 +102,9 @@ export class Attachment { }; public isPublicKey = (): boolean => { + if (this.treatAsValue) { + return this.treatAsValue === 'publicKey'; + } return ( this.type === 'application/pgp-keys' || /^(0|0x)?[A-F0-9]{8}([A-F0-9]{8})?.*\.asc$/g.test(this.name) || // name starts with a key id From c3088864ed4575a1bd4b97eb687b5e407b975305 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 3 Mar 2023 12:56:22 +0000 Subject: [PATCH 5/5] test with known pubkey --- test/source/tests/decrypt.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/test/source/tests/decrypt.ts b/test/source/tests/decrypt.ts index 9b38626e779..d24af61cb2a 100644 --- a/test/source/tests/decrypt.ts +++ b/test/source/tests/decrypt.ts @@ -924,18 +924,36 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw== testWithBrowser('ci.tests.gmail', async (t, browser) => { const acctEmail = 'ci.tests.gmail@flowcrypt.test'; const threadId = '1869220e0c8f16dd'; - const inboxPage = await browser.newExtensionInboxPage(t, acctEmail, threadId); + let inboxPage = await browser.newExtensionInboxPage(t, acctEmail, threadId); await inboxPage.waitAll('iframe'); expect((await inboxPage.getFramesUrls(['pgp_block.htm'])).length).to.equal(1); expect((await inboxPage.getFramesUrls(['pgp_pubkey.htm'])).length).to.equal(1); expect((await inboxPage.getFramesUrls(['attachment.htm'])).length).to.equal(0); // invisible - const pgpBlock = await inboxPage.getFrame(['pgp_block.htm']); - await BrowserRecipe.pgpBlockCheck(t, pgpBlock, { + await BrowserRecipe.pgpBlockCheck(t, await inboxPage.getFrame(['pgp_block.htm']), { content: ['Sent with Proton Mail secure email.'], encryption: 'encrypted', signature: 'could not verify signature: missing pubkey 616D596BC2065D48', }); await inboxPage.close(); + const dbPage = await browser.newExtensionPage(t, 'chrome/dev/ci_unit_test.htm'); + // add the pubkey of the sender + await dbPage.page.evaluate(async (pubkey: string) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const key = await (window as any).KeyUtil.parse(pubkey); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await (window as any).ContactStore.update(undefined, 'schlemazle@proton.me', { pubkey: key }); + }, testConstants.protonPubkey); + await dbPage.close(); + inboxPage = await browser.newExtensionInboxPage(t, acctEmail, threadId); + await inboxPage.waitAll('iframe'); + expect((await inboxPage.getFramesUrls(['pgp_block.htm'])).length).to.equal(1); + expect((await inboxPage.getFramesUrls(['pgp_pubkey.htm'])).length).to.equal(1); + expect((await inboxPage.getFramesUrls(['attachment.htm'])).length).to.equal(0); // invisible + await BrowserRecipe.pgpBlockCheck(t, await inboxPage.getFrame(['pgp_block.htm']), { + content: ['Sent with Proton Mail secure email.'], + encryption: 'encrypted', + signature: 'signed', + }); t.pass(); }) );