From 7a465606bce877295def8cdd56db8c43cecbfe9e Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 26 Nov 2024 02:47:17 +0100 Subject: [PATCH] Resolve #1849 --- dev/App/User.js | 11 ++- dev/Model/Message.js | 140 +++++++++++++++++++++++++++ dev/View/User/MailBox/MessageView.js | 117 +--------------------- 3 files changed, 154 insertions(+), 114 deletions(-) diff --git a/dev/App/User.js b/dev/App/User.js index aafec7da3..7e87ebffb 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -1,6 +1,7 @@ import 'External/User/ko'; import { SMAudio } from 'Common/Audio'; +import { arrayLength } from 'Common/Utils'; import { mailToHelper, setLayoutResizer, dropdownsDetectVisibility, loadAccountsAndIdentities } from 'Common/UtilsUser'; import { @@ -240,7 +241,15 @@ export class AppUser extends AbstractApp { showMessageComposer(params = []) { - showScreenPopup(ComposePopupView, params); + let msg = params[1]; + if (1 == arrayLength(msg)) { + msg = msg[0]; + } + if (msg) { + msg.decrypt().then((/*success*/)=>showScreenPopup(ComposePopupView, params)); + } else { + showScreenPopup(ComposePopupView, params); + } } } diff --git a/dev/Model/Message.js b/dev/Model/Message.js index ebb936965..9b320c14e 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -24,6 +24,10 @@ import Remote from 'Remote/User/Fetch'; import { MimeToMessage } from 'Mime/Utils'; +import { PgpUserStore } from 'Stores/User/Pgp'; +import { IdentityUserStore } from 'Stores/User/Identity'; +import { Passphrases } from 'Storage/Passphrases'; + const PreviewHTML = ` @@ -598,4 +602,140 @@ export class MessageModel extends AbstractModel { return result.html || plainToHtml(this.plain()); } + async decrypt() { + const msg = this; + if (msg.pgpEncrypted() && !msg.pgpDecrypted()) { + return await msg.pgpDecrypt().then(()=>msg.pgpDecrypted()); + } + if (msg.smimeEncrypted() && !msg.smimeDecrypted()) { + return await msg.smimeDecrypt().then(()=>msg.smimeDecrypted()); + } + return true; + } +/* + async decrypted() { + return msg.pgpDecrypted() || msg.smimeDecrypted()); + } +*/ + async pgpDecrypt() { + const oMessage = this, + data = oMessage.pgpEncrypted(); + delete data.error; + await PgpUserStore.decrypt(oMessage).then(result => { + if (!result) { + // TODO: translate + throw Error('Decryption failed, canceled or not possible'); + } + oMessage.pgpDecrypted(true); + if (result.data) { + MimeToMessage(result.data, oMessage); + oMessage.html() ? oMessage.viewHtml() : oMessage.viewPlain(); + if (result.signatures?.length) { + oMessage.pgpSigned({ + signatures: result.signatures, + success: !!result.signatures.length + }); + } + } + }) + .catch(e => { + data.error = e.message; + }) + .finally(() => { + oMessage.pgpEncrypted(data); + }); + } + + pgpVerify(/*self, event*/) { + const oMessage = this; + PgpUserStore.verify(oMessage).then(result => { + if (result) { + oMessage.pgpSigned(result); + } else { + alert('Verification failed or no valid public key found'); + } +/* + if (result?.success) { + i18n('CRYPTO/GOOD_SIGNATURE', { + USER: validKey.user + ' (' + validKey.id + ')' + }); + message.getText() + } else { + const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null, + additional = keyIds + ? keyIds.map(item => item?.toHex?.()).filter(v => v).join(', ') + : ''; + + i18n('CRYPTO/ERROR', { + TYPE: 'OpenPGP', + ERROR: 'message' + }) + (additional ? ' (' + additional + ')' : ''); + } +*/ + }); + } + + async smimeDecrypt() { + const message = this; + const addresses = message.from.concat(message.to, message.cc, message.bcc).map(item => item.email), + identity = IdentityUserStore.find(item => addresses.includes(item.email)), + data = message.smimeEncrypted(); // { partId: "1" } + if (data && identity) { + delete data.error; + let pass, params = { ...data }; // clone + params.folder = message.folder; + params.uid = message.uid; +// params.bodyPart = params.bodyPart?.raw; + params.certificate = identity.smimeCertificate(); + params.privateKey = identity.smimeKey(); + if (identity.smimeKeyEncrypted()) { + pass = await Passphrases.ask(identity, + i18n('SMIME/PRIVATE_KEY_OF', {EMAIL: identity.email}), + 'CRYPTO/DECRYPT' + ); + if (!pass) { + return; + } + params.passphrase = pass?.password; + } + await Remote.post('SMimeDecryptMessage', null, params).then(response => { + if (response?.Result?.data) { + message.smimeDecrypted(true); + MimeToMessage(response.Result.data, message); + message.html() ? message.viewHtml() : message.viewPlain(); + pass && pass.remember && Passphrases.handle(identity, pass.password); + if ('signed' in response.Result) { + message.smimeSigned(response.Result.signed); + } + } + }).catch(e => { + data.error = e.message + }) + .finally(() => { + message.smimeEncrypted(data); + }); + } + } + + smimeVerify(/*self, event*/) { + const message = this, + data = message.smimeSigned(); // { partId: "1", micAlg: "pgp-sha256" } + if (data) { + const params = { ...data }; // clone + params.folder = message.folder; + params.uid = message.uid; + params.bodyPart = data.bodyPart?.raw; + params.sigPart = data.sigPart?.bodyRaw; + Remote.post('SMimeVerifyMessage', null, params).then(response => { + if (response?.Result) { + if (response.Result.body) { + MimeToMessage(response.Result.body, message); + message.html() ? message.viewHtml() : message.viewPlain(); + } + data.success = response.Result.success; + message.smimeSigned(data); + } + }); + } + } } diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index db3cbd106..2c0bafec6 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -56,9 +56,6 @@ import { showScreenPopup } from 'Knoin/Knoin'; import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport'; import { GnuPGUserStore } from 'Stores/User/GnuPG'; import { OpenPGPUserStore } from 'Stores/User/OpenPGP'; -import { IdentityUserStore } from 'Stores/User/Identity'; - -import { Passphrases } from 'Storage/Passphrases'; const oMessageScrollerDom = () => elementById('messageItem') || {}, @@ -610,125 +607,19 @@ export class MailMessageView extends AbstractViewRight { } pgpDecrypt() { - const oMessage = currentMessage(), - data = oMessage.pgpEncrypted(); - delete data.error; - PgpUserStore.decrypt(oMessage).then(result => { - if (!result) { - // TODO: translate - throw Error('Decryption failed, canceled or not possible'); - } - oMessage.pgpDecrypted(true); - if (result.data) { - MimeToMessage(result.data, oMessage); - oMessage.html() ? oMessage.viewHtml() : oMessage.viewPlain(); - if (result.signatures?.length) { - oMessage.pgpSigned({ - signatures: result.signatures, - success: !!result.signatures.length - }); - } - } - }) - .catch(e => { - data.error = e.message; - }) - .finally(() => { - oMessage.pgpEncrypted(data); - }); + currentMessage().decrypt(); } pgpVerify(/*self, event*/) { - const oMessage = currentMessage()/*, ctrl = event.target.closest('.openpgp-control')*/; - PgpUserStore.verify(oMessage).then(result => { - if (result) { - oMessage.pgpSigned(result); - } else { - alert('Verification failed or no valid public key found'); - } -/* - if (result?.success) { - i18n('CRYPTO/GOOD_SIGNATURE', { - USER: validKey.user + ' (' + validKey.id + ')' - }); - message.getText() - } else { - const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null, - additional = keyIds - ? keyIds.map(item => item?.toHex?.()).filter(v => v).join(', ') - : ''; - - i18n('CRYPTO/ERROR', { - TYPE: 'OpenPGP', - ERROR: 'message' - }) + (additional ? ' (' + additional + ')' : ''); - } -*/ - }); + currentMessage().pgpVerify(); } async smimeDecrypt() { - const message = currentMessage(); - const addresses = message.from.concat(message.to, message.cc, message.bcc).map(item => item.email), - identity = IdentityUserStore.find(item => addresses.includes(item.email)), - data = message.smimeEncrypted(); // { partId: "1" } - if (data && identity) { - delete data.error; - let pass, params = { ...data }; // clone - params.folder = message.folder; - params.uid = message.uid; -// params.bodyPart = params.bodyPart?.raw; - params.certificate = identity.smimeCertificate(); - params.privateKey = identity.smimeKey(); - if (identity.smimeKeyEncrypted()) { - pass = await Passphrases.ask(identity, - i18n('SMIME/PRIVATE_KEY_OF', {EMAIL: identity.email}), - 'CRYPTO/DECRYPT' - ); - if (!pass) { - return; - } - params.passphrase = pass?.password; - } - Remote.post('SMimeDecryptMessage', null, params).then(response => { - if (response?.Result?.data) { - message.smimeDecrypted(true); - MimeToMessage(response.Result.data, message); - message.html() ? message.viewHtml() : message.viewPlain(); - pass && pass.remember && Passphrases.handle(identity, pass.password); - if ('signed' in response.Result) { - message.smimeSigned(response.Result.signed); - } - } - }).catch(e => { - data.error = e.message - }) - .finally(() => { - message.smimeEncrypted(data); - }); - } + currentMessage().decrypt(); } smimeVerify(/*self, event*/) { - const message = currentMessage(), - data = message.smimeSigned(); // { partId: "1", micAlg: "pgp-sha256" } - if (data) { - const params = { ...data }; // clone - params.folder = message.folder; - params.uid = message.uid; - params.bodyPart = data.bodyPart?.raw; - params.sigPart = data.sigPart?.bodyRaw; - Remote.post('SMimeVerifyMessage', null, params).then(response => { - if (response?.Result) { - if (response.Result.body) { - MimeToMessage(response.Result.body, message); - message.html() ? message.viewHtml() : message.viewPlain(); - } - data.success = response.Result.success; - message.smimeSigned(data); - } - }); - } + currentMessage().smimeVerify(); } }