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);
}
});
}