From 453e73f71ac9f77daecfcd80c5027b039553b30c Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 21 Feb 2024 23:28:53 +0100 Subject: [PATCH] Cache S/MIME passphrases when "remember" is checked --- dev/Storage/Passphrases.js | 9 +++++++- dev/Stores/User/GnuPG.js | 34 +++++++++++++--------------- dev/Stores/User/OpenPGP.js | 12 ++++------ dev/View/Popup/Compose.js | 12 ++++++++-- dev/View/User/MailBox/MessageView.js | 11 +++++---- 5 files changed, 46 insertions(+), 32 deletions(-) diff --git a/dev/Storage/Passphrases.js b/dev/Storage/Passphrases.js index 4ecf6ccda..e9d2ec5e7 100644 --- a/dev/Storage/Passphrases.js +++ b/dev/Storage/Passphrases.js @@ -1 +1,8 @@ -export const Passphrases = new Map(); +import { AskPopupView } from 'View/Popup/Ask'; + +export const Passphrases = new WeakMap(); + +Passphrases.ask = async (key, sAskDesc, btnText) => + Passphrases.has(key) + ? {password:Passphrases.get(key)/*, remember:false*/} + : await AskPopupView.password(sAskDesc, btnText); diff --git a/dev/Stores/User/GnuPG.js b/dev/Stores/User/GnuPG.js index 8b79d8eb5..cd121194d 100644 --- a/dev/Stores/User/GnuPG.js +++ b/dev/Stores/User/GnuPG.js @@ -9,20 +9,10 @@ import Remote from 'Remote/User/Fetch'; import { showScreenPopup } from 'Knoin/Knoin'; import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey'; -import { AskPopupView } from 'View/Popup/Ask'; import { Passphrases } from 'Storage/Passphrases'; const - askPassphrase = async (privateKey, btnTxt = 'SIGN') => { - const key = privateKey.id, - pass = Passphrases.has(key) - ? {password:Passphrases.get(key), remember:false} - : await AskPopupView.password('GnuPG key
' + key + ' ' + privateKey.emails[0], 'CRYPTO/'+btnTxt); - pass && pass.remember && Passphrases.set(key, pass.password); - return pass?.password; - }, - findGnuPGKey = (keys, query/*, sign*/) => keys.find(key => // key[sign ? 'can_sign' : 'can_decrypt'] @@ -77,14 +67,22 @@ export const GnuPGUserStore = new class { ); } }; + if (isPrivate) { + key.password = async (btnTxt = 'SIGN') => { + const pass = await Passphrases.ask( + key, + 'GnuPG key
' + key.id + ' ' + key.emails[0], + 'CRYPTO/'+btnTxt + ); + pass && pass.remember && Passphrases.set(key, pass.password); + return pass?.password; + }; + } key.fetch = async callback => { if (key.armor) { callback && callback(); } else { - let pass = ''; - if (isPrivate) { - pass = await askPassphrase(key, 'POPUP_VIEW_TITLE'); - } + let pass = isPrivate ? await key.password('POPUP_VIEW_TITLE') : ''; if (null != pass) { const result = await Remote.post('GnupgExportKey', null, { keyId: key.id, @@ -95,7 +93,7 @@ export const GnuPGUserStore = new class { key.armor = result.Result; callback && callback(); } else { - Passphrases.delete(key.id); + Passphrases.delete(key); } } } @@ -180,7 +178,7 @@ export const GnuPGUserStore = new class { uid: message.uid, partId: pgpInfo.partId, keyId: key.id, - passphrase: await askPassphrase(key, 'DECRYPT'), + passphrase: await key.password('DECRYPT'), data: '' // message.plain() optional } if (null !== params.passphrase) { @@ -188,7 +186,7 @@ export const GnuPGUserStore = new class { if (result?.Result && false !== result.Result.data) { return result.Result; } - Passphrases.delete(key.id); + Passphrases.delete(key); } } } @@ -217,7 +215,7 @@ export const GnuPGUserStore = new class { } async sign(privateKey) { - return await askPassphrase(privateKey); + return await privateKey.password(); } }; diff --git a/dev/Stores/User/OpenPGP.js b/dev/Stores/User/OpenPGP.js index 79567e185..df2a1172d 100644 --- a/dev/Stores/User/OpenPGP.js +++ b/dev/Stores/User/OpenPGP.js @@ -10,7 +10,6 @@ import Remote from 'Remote/User/Fetch'; import { showScreenPopup } from 'Knoin/Knoin'; import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey'; -import { AskPopupView } from 'View/Popup/Ask'; import { Passphrases } from 'Storage/Passphrases'; @@ -25,12 +24,11 @@ const return privateKey.key; } const key = privateKey.id, - pass = Passphrases.has(key) - ? {password:Passphrases.get(key), remember:false} - : await AskPopupView.password( - 'OpenPGP.js key
' + key + ' ' + privateKey.emails[0], - 'CRYPTO/'+btnTxt - ); + pass = await Passphrases.ask( + key, + 'OpenPGP.js key
' + key + ' ' + privateKey.emails[0], + 'CRYPTO/'+btnTxt + ); if (pass) { const passphrase = pass.password, result = await openpgp.decryptKey({ diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 7bec4abdc..19ad42150 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -34,6 +34,7 @@ import { OpenPGPUserStore } from 'Stores/User/OpenPGP'; import { GnuPGUserStore } from 'Stores/User/GnuPG'; //import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport'; import { SMimeUserStore } from 'Stores/User/SMime'; +import { Passphrases } from 'Storage/Passphrases'; import { MessageUserStore } from 'Stores/User/Message'; import { MessagelistUserStore } from 'Stores/User/Messagelist'; @@ -470,7 +471,8 @@ export class ComposePopupView extends AbstractViewPopup { } sendCommand() { - let sSentFolder = this.currentIdentity()?.sentFolder?.() || FolderUserStore.sentFolder(); + const identity = this.currentIdentity(); + let sSentFolder = identity?.sentFolder?.() || FolderUserStore.sentFolder(); this.attachmentsInProcessError(false); this.attachmentsInErrorError(false); @@ -520,6 +522,7 @@ export class ComposePopupView extends AbstractViewPopup { } this.savedErrorDesc(msg); } else { + params.signPassphrase && Passphrases.delete(identity); this.sendError(true); this.sendErrorDesc(getNotification(iError, data?.ErrorMessage) || getNotification(Notifications.CantSendMessage)); @@ -1539,8 +1542,13 @@ export class ComposePopupView extends AbstractViewPopup { params.signCertificate = identity.smimeCertificate(); params.signPrivateKey = identity.smimeKey(); if (identity.smimeKeyEncrypted()) { - const pass = await AskPopupView.password('S/MIME private key', 'CRYPTO/SIGN'); + const pass = await Passphrases.ask( + params.signPrivateKey, + 'S/MIME private key ' + identity.email(), + 'CRYPTO/DECRYPT' + ); params.signPassphrase = pass?.password; +// pass && pass.remember && Passphrases.set(identity, pass.password); } } } diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index aefa19e44..44db3b659 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -56,7 +56,7 @@ import { GnuPGUserStore } from 'Stores/User/GnuPG'; import { OpenPGPUserStore } from 'Stores/User/OpenPGP'; import { IdentityUserStore } from 'Stores/User/Identity'; -import { AskPopupView } from 'View/Popup/Ask'; +import { Passphrases } from 'Storage/Passphrases'; const oMessageScrollerDom = () => elementById('messageItem') || {}, @@ -624,7 +624,7 @@ export class MailMessageView extends AbstractViewRight { async smimeDecrypt() { const message = currentMessage(); - let data = message.smimeEncrypted(); // { partId: "1" } + let pass, data = message.smimeEncrypted(); // { partId: "1" } const addresses = message.from.concat(message.to, message.cc, message.bcc).map(item => item.email), identity = IdentityUserStore.find(item => addresses.includes(item.email())); if (data && identity) { @@ -635,15 +635,18 @@ export class MailMessageView extends AbstractViewRight { data.certificate = identity.smimeCertificate(); data.privateKey = identity.smimeKey(); if (identity.smimeKeyEncrypted()) { - const pass = await AskPopupView.password('S/MIME private key', 'CRYPTO/DECRYPT'); + pass = await Passphrases.ask(identity, 'S/MIME private key ' + identity.email(), 'CRYPTO/DECRYPT'); + if (!pass) { + return; + } data.passphrase = pass?.password; } - Remote.post('SMimeDecryptMessage', null, data).then(response => { if (response?.Result) { message.smimeDecrypted(true); MimeToMessage(response.Result, message); message.html() ? message.viewHtml() : message.viewPlain(); + pass && pass.remember && Passphrases.set(identity, pass.password); } }); }