Cache S/MIME passphrases when "remember" is checked

This commit is contained in:
the-djmaze 2024-02-21 23:28:53 +01:00
parent 9941ff61f7
commit 453e73f71a
5 changed files with 46 additions and 32 deletions

View file

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

View file

@ -9,20 +9,10 @@ import Remote from 'Remote/User/Fetch';
import { showScreenPopup } from 'Knoin/Knoin'; import { showScreenPopup } from 'Knoin/Knoin';
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey'; import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
import { AskPopupView } from 'View/Popup/Ask';
import { Passphrases } from 'Storage/Passphrases'; import { Passphrases } from 'Storage/Passphrases';
const 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<br>' + key + ' ' + privateKey.emails[0], 'CRYPTO/'+btnTxt);
pass && pass.remember && Passphrases.set(key, pass.password);
return pass?.password;
},
findGnuPGKey = (keys, query/*, sign*/) => findGnuPGKey = (keys, query/*, sign*/) =>
keys.find(key => keys.find(key =>
// key[sign ? 'can_sign' : 'can_decrypt'] // 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<br>' + key.id + ' ' + key.emails[0],
'CRYPTO/'+btnTxt
);
pass && pass.remember && Passphrases.set(key, pass.password);
return pass?.password;
};
}
key.fetch = async callback => { key.fetch = async callback => {
if (key.armor) { if (key.armor) {
callback && callback(); callback && callback();
} else { } else {
let pass = ''; let pass = isPrivate ? await key.password('POPUP_VIEW_TITLE') : '';
if (isPrivate) {
pass = await askPassphrase(key, 'POPUP_VIEW_TITLE');
}
if (null != pass) { if (null != pass) {
const result = await Remote.post('GnupgExportKey', null, { const result = await Remote.post('GnupgExportKey', null, {
keyId: key.id, keyId: key.id,
@ -95,7 +93,7 @@ export const GnuPGUserStore = new class {
key.armor = result.Result; key.armor = result.Result;
callback && callback(); callback && callback();
} else { } else {
Passphrases.delete(key.id); Passphrases.delete(key);
} }
} }
} }
@ -180,7 +178,7 @@ export const GnuPGUserStore = new class {
uid: message.uid, uid: message.uid,
partId: pgpInfo.partId, partId: pgpInfo.partId,
keyId: key.id, keyId: key.id,
passphrase: await askPassphrase(key, 'DECRYPT'), passphrase: await key.password('DECRYPT'),
data: '' // message.plain() optional data: '' // message.plain() optional
} }
if (null !== params.passphrase) { if (null !== params.passphrase) {
@ -188,7 +186,7 @@ export const GnuPGUserStore = new class {
if (result?.Result && false !== result.Result.data) { if (result?.Result && false !== result.Result.data) {
return result.Result; return result.Result;
} }
Passphrases.delete(key.id); Passphrases.delete(key);
} }
} }
} }
@ -217,7 +215,7 @@ export const GnuPGUserStore = new class {
} }
async sign(privateKey) { async sign(privateKey) {
return await askPassphrase(privateKey); return await privateKey.password();
} }
}; };

View file

@ -10,7 +10,6 @@ import Remote from 'Remote/User/Fetch';
import { showScreenPopup } from 'Knoin/Knoin'; import { showScreenPopup } from 'Knoin/Knoin';
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey'; import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
import { AskPopupView } from 'View/Popup/Ask';
import { Passphrases } from 'Storage/Passphrases'; import { Passphrases } from 'Storage/Passphrases';
@ -25,9 +24,8 @@ const
return privateKey.key; return privateKey.key;
} }
const key = privateKey.id, const key = privateKey.id,
pass = Passphrases.has(key) pass = await Passphrases.ask(
? {password:Passphrases.get(key), remember:false} key,
: await AskPopupView.password(
'OpenPGP.js key<br>' + key + ' ' + privateKey.emails[0], 'OpenPGP.js key<br>' + key + ' ' + privateKey.emails[0],
'CRYPTO/'+btnTxt 'CRYPTO/'+btnTxt
); );

View file

@ -34,6 +34,7 @@ import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
import { GnuPGUserStore } from 'Stores/User/GnuPG'; import { GnuPGUserStore } from 'Stores/User/GnuPG';
//import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport'; //import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport';
import { SMimeUserStore } from 'Stores/User/SMime'; import { SMimeUserStore } from 'Stores/User/SMime';
import { Passphrases } from 'Storage/Passphrases';
import { MessageUserStore } from 'Stores/User/Message'; import { MessageUserStore } from 'Stores/User/Message';
import { MessagelistUserStore } from 'Stores/User/Messagelist'; import { MessagelistUserStore } from 'Stores/User/Messagelist';
@ -470,7 +471,8 @@ export class ComposePopupView extends AbstractViewPopup {
} }
sendCommand() { sendCommand() {
let sSentFolder = this.currentIdentity()?.sentFolder?.() || FolderUserStore.sentFolder(); const identity = this.currentIdentity();
let sSentFolder = identity?.sentFolder?.() || FolderUserStore.sentFolder();
this.attachmentsInProcessError(false); this.attachmentsInProcessError(false);
this.attachmentsInErrorError(false); this.attachmentsInErrorError(false);
@ -520,6 +522,7 @@ export class ComposePopupView extends AbstractViewPopup {
} }
this.savedErrorDesc(msg); this.savedErrorDesc(msg);
} else { } else {
params.signPassphrase && Passphrases.delete(identity);
this.sendError(true); this.sendError(true);
this.sendErrorDesc(getNotification(iError, data?.ErrorMessage) this.sendErrorDesc(getNotification(iError, data?.ErrorMessage)
|| getNotification(Notifications.CantSendMessage)); || getNotification(Notifications.CantSendMessage));
@ -1539,8 +1542,13 @@ export class ComposePopupView extends AbstractViewPopup {
params.signCertificate = identity.smimeCertificate(); params.signCertificate = identity.smimeCertificate();
params.signPrivateKey = identity.smimeKey(); params.signPrivateKey = identity.smimeKey();
if (identity.smimeKeyEncrypted()) { 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; params.signPassphrase = pass?.password;
// pass && pass.remember && Passphrases.set(identity, pass.password);
} }
} }
} }

View file

@ -56,7 +56,7 @@ import { GnuPGUserStore } from 'Stores/User/GnuPG';
import { OpenPGPUserStore } from 'Stores/User/OpenPGP'; import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
import { IdentityUserStore } from 'Stores/User/Identity'; import { IdentityUserStore } from 'Stores/User/Identity';
import { AskPopupView } from 'View/Popup/Ask'; import { Passphrases } from 'Storage/Passphrases';
const const
oMessageScrollerDom = () => elementById('messageItem') || {}, oMessageScrollerDom = () => elementById('messageItem') || {},
@ -624,7 +624,7 @@ export class MailMessageView extends AbstractViewRight {
async smimeDecrypt() { async smimeDecrypt() {
const message = currentMessage(); 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), const addresses = message.from.concat(message.to, message.cc, message.bcc).map(item => item.email),
identity = IdentityUserStore.find(item => addresses.includes(item.email())); identity = IdentityUserStore.find(item => addresses.includes(item.email()));
if (data && identity) { if (data && identity) {
@ -635,15 +635,18 @@ export class MailMessageView extends AbstractViewRight {
data.certificate = identity.smimeCertificate(); data.certificate = identity.smimeCertificate();
data.privateKey = identity.smimeKey(); data.privateKey = identity.smimeKey();
if (identity.smimeKeyEncrypted()) { 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; data.passphrase = pass?.password;
} }
Remote.post('SMimeDecryptMessage', null, data).then(response => { Remote.post('SMimeDecryptMessage', null, data).then(response => {
if (response?.Result) { if (response?.Result) {
message.smimeDecrypted(true); message.smimeDecrypted(true);
MimeToMessage(response.Result, message); MimeToMessage(response.Result, message);
message.html() ? message.viewHtml() : message.viewPlain(); message.html() ? message.viewHtml() : message.viewPlain();
pass && pass.remember && Passphrases.set(identity, pass.password);
} }
}); });
} }