Revamp PGP management due to implementing Mailvelop and PEAR Crypt_GPG

This commit is contained in:
the-djmaze 2022-01-19 18:24:07 +01:00
parent 43a1196dbb
commit a47397ef09
16 changed files with 885 additions and 633 deletions

View file

@ -72,7 +72,7 @@ class AddOpenPgpKeyPopupView extends AbstractViewPopup {
openpgpKeyring.store();
rl.app.reloadOpenPgpKeys();
PgpUserStore.reloadOpenPgpKeys();
if (this.keyError()) {
return false;

View file

@ -34,6 +34,7 @@ import { MessageUserStore } from 'Stores/User/Message';
import Remote from 'Remote/User/Fetch';
import { ComposeAttachmentModel } from 'Model/ComposeAttachment';
import { EmailModel } from 'Model/Email';
import { decorateKoCommands, isPopupVisible, showScreenPopup } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
@ -41,7 +42,6 @@ import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { FolderSystemPopupView } from 'View/Popup/FolderSystem';
import { AskPopupView } from 'View/Popup/Ask';
import { ContactsPopupView } from 'View/Popup/Contacts';
import { ComposeOpenPgpPopupView } from 'View/Popup/ComposeOpenPgp';
import { ThemeStore } from 'Stores/Theme';
@ -131,7 +131,7 @@ class ComposePopupView extends AbstractViewPopup {
this.bSkipNextHide = false;
this.capaOpenPGP = !!PgpUserStore.openpgp;
this.capaOpenPGP = PgpUserStore.isSupported();
this.identities = IdentityUserStore;
@ -169,6 +169,11 @@ class ComposePopupView extends AbstractViewPopup {
showBcc: false,
showReplyTo: false,
pgpSign: false,
pgpEncrypt: false,
canPgpSign: false,
canPgpEncrypt: false,
draftsFolder: '',
draftUid: 0,
sending: false,
@ -265,7 +270,6 @@ class ComposePopupView extends AbstractViewPopup {
},
canBeSentOrSaved: () => !this.sending() && !this.saving()
});
this.addSubscribables({
@ -275,16 +279,26 @@ class ComposePopupView extends AbstractViewPopup {
sendSuccessButSaveError: value => !value && this.savedErrorDesc(''),
currentIdentity: value => {
this.canPgpSign(false);
value && PgpUserStore.hasPrivateKeyForEmail(value.email()).then(result => {
console.log({canPgpSign:result});
this.canPgpSign(result)
});
},
cc: value => {
if (false === this.showCc() && value.length) {
this.showCc(true);
}
this.initPgpEncrypt();
},
bcc: value => {
if (false === this.showBcc() && value.length) {
this.showBcc(true);
}
this.initPgpEncrypt();
},
replyTo: value => {
@ -303,6 +317,7 @@ class ComposePopupView extends AbstractViewPopup {
if (this.emptyToError() && value.length) {
this.emptyToError(false);
}
this.initPgpEncrypt();
},
attachmentsInProcess: value => {
@ -551,19 +566,6 @@ class ComposePopupView extends AbstractViewPopup {
rl.app.getAutocomplete(oData.term, aData => fResponse(aData.map(oEmailItem => oEmailItem.toLine(false))));
}
openOpenPgpPopup() {
if (PgpUserStore.openpgp && !this.oEditor.isHtml()) {
showScreenPopup(ComposeOpenPgpPopupView, [
result => this.editor(editor => editor.setPlain(result)),
this.oEditor.getData(false),
this.currentIdentity(),
this.to(),
this.cc(),
this.bcc()
]);
}
}
reloadDraftFolder() {
const draftsFolder = FolderUserStore.draftsFolder();
if (draftsFolder && UNUSED_OPTION_VALUE !== draftsFolder) {
@ -1419,6 +1421,9 @@ class ComposePopupView extends AbstractViewPopup {
this.showBcc(false);
this.showReplyTo(false);
this.pgpSign(false);
this.pgpEncrypt(false);
delegateRunOnDestroy(this.attachments());
this.attachments([]);
@ -1442,6 +1447,31 @@ class ComposePopupView extends AbstractViewPopup {
item => item.id
);
}
initPgpEncrypt() {
const email = new EmailModel();
return PgpUserStore.hasPublicKeyForEmails([
// this.currentIdentity.email(),
this.to(),
this.cc(),
this.bcc()
].join(',').split(',').map(value => {
email.clear();
email.parse(value.trim());
return email.email || false;
}).filter(v => v), 1).then(result => {
console.log({canPgpEncrypt:result});
this.canPgpEncrypt(result);
});
}
togglePgpSign() {
this.pgpSign(!this.pgpSign()/* && this.canPgpSign()*/);
}
togglePgpEncrypt() {
this.pgpEncrypt(!this.pgpEncrypt()/* && this.canPgpEncrypt()*/);
}
}
export { ComposePopupView, ComposePopupView as default };

View file

@ -1,385 +0,0 @@
import ko from 'ko';
import { pString, defaultOptionsAfterRender } from 'Common/Utils';
import { Scope } from 'Common/Enums';
import { i18n } from 'Common/Translator';
import { PgpUserStore } from 'Stores/User/Pgp';
import { EmailModel } from 'Model/Email';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
const KEY_NAME_SUBSTR = -8,
i18nPGP = (key, params) => i18n('PGP_NOTIFICATIONS/' + key, params);
class ComposeOpenPgpPopupView extends AbstractViewPopup {
constructor() {
super('ComposeOpenPgp');
this.publicKeysOptionsCaption = i18nPGP('ADD_A_PUBLICK_KEY');
this.privateKeysOptionsCaption = i18nPGP('SELECT_A_PRIVATE_KEY');
this.addObservables({
notification: '',
sign: false,
encrypt: false,
password: '',
text: '',
selectedPrivateKey: null,
selectedPublicKey: null,
signKey: null,
submitRequest: false
});
this.encryptKeys = ko.observableArray();
this.addComputables({
encryptKeysView: () => this.encryptKeys.map(oKey => (oKey ? oKey.key : null)).filter(v => v),
privateKeysOptions: () => {
const opts = PgpUserStore.openpgpkeysPrivate().map(oKey => {
if (this.signKey() && this.signKey().key.id === oKey.id) {
return null;
}
return oKey.users.map(user => ({
id: oKey.guid,
name: '(' + oKey.id.slice(KEY_NAME_SUBSTR).toUpperCase() + ') ' + user,
key: oKey
}));
});
return opts.flat().filter(v => v);
},
publicKeysOptions: () => {
const opts = PgpUserStore.openpgpkeysPublic().map(oKey => {
if (this.encryptKeysView().includes(oKey)) {
return null;
}
return oKey.users.map(user => ({
id: oKey.guid,
name: '(' + oKey.id.slice(KEY_NAME_SUBSTR).toUpperCase() + ') ' + user,
key: oKey
}));
});
return opts.flat().filter(v => v);
}
});
this.resultCallback = null;
this.selectedPrivateKey.subscribe((value) => {
if (value) {
this.selectCommand();
this.updateCommand();
}
});
this.selectedPublicKey.subscribe((value) => {
if (value) {
this.addCommand();
}
});
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
this.deletePublickKey = this.deletePublickKey.bind(this);
decorateKoCommands(this, {
doCommand: self => !self.submitRequest() && (self.sign() || self.encrypt()),
selectCommand: 1,
addCommand: 1,
updateCommand: 1,
});
}
doCommand() {
let result = true,
privateKey = null,
aPublicKeys = [];
this.submitRequest(true);
if (result && this.sign()) {
if (!this.signKey()) {
this.notification(i18nPGP('NO_PRIVATE_KEY_FOUND'));
result = false;
} else if (!this.signKey().key) {
this.notification(
i18nPGP('NO_PRIVATE_KEY_FOUND_FOR', {
EMAIL: this.signKey().email
})
);
result = false;
}
if (result) {
const privateKeys = this.signKey().key.getNativeKeys();
privateKey = privateKeys[0] || null;
try {
if (privateKey) {
privateKey.decrypt(pString(this.password()));
}
} catch (e) {
privateKey = null;
}
if (!privateKey) {
this.notification(i18nPGP('NO_PRIVATE_KEY_FOUND'));
result = false;
}
}
}
if (result && this.encrypt()) {
if (this.encryptKeys.length) {
aPublicKeys = [];
this.encryptKeys.forEach(oKey => {
if (oKey && oKey.key) {
aPublicKeys = aPublicKeys.concat(oKey.key.getNativeKeys().flat(Infinity).filter(v => v));
} else if (oKey && oKey.email) {
this.notification(
i18nPGP('NO_PUBLIC_KEYS_FOUND_FOR', {
EMAIL: oKey.email
})
);
result = false;
}
});
if (result && (!aPublicKeys.length || this.encryptKeys.length !== aPublicKeys.length)) {
result = false;
}
} else {
this.notification(i18nPGP('NO_PUBLIC_KEYS_FOUND'));
result = false;
}
}
if (result && this.resultCallback) {
setTimeout(() => {
let pgpPromise = null;
try {
if (aPublicKeys.length) {
if (privateKey) {
pgpPromise = PgpUserStore.openpgp.encrypt({
data: this.text(),
publicKeys: aPublicKeys,
privateKeys: [privateKey]
});
} else {
pgpPromise = PgpUserStore.openpgp.encrypt({
data: this.text(),
publicKeys: aPublicKeys
});
}
} else if (privateKey) {
pgpPromise = PgpUserStore.openpgp.sign({
data: this.text(),
privateKeys: [privateKey]
});
}
} catch (e) {
console.log(e);
this.notification(
i18nPGP('PGP_ERROR', {
ERROR: '' + e
})
);
}
if (pgpPromise) {
try {
pgpPromise
.then((mData) => {
this.resultCallback(mData.data);
this.cancelCommand();
})
.catch((e) => {
this.notification(
i18nPGP('PGP_ERROR', {
ERROR: '' + e
})
);
});
} catch (e) {
this.notification(
i18nPGP('PGP_ERROR', {ERROR: '' + e})
);
}
}
this.submitRequest(false);
}, 20);
} else {
this.submitRequest(false);
}
return result;
}
selectCommand() {
const keyId = this.selectedPrivateKey(),
option = keyId ? this.privateKeysOptions().find(item => item && keyId === item.id) : null;
if (option) {
this.signKey({
empty: !option.key,
selected: ko.observable(!!option.key),
users: option.key.users,
hash: option.key.id.slice(KEY_NAME_SUBSTR).toUpperCase(),
key: option.key
});
}
}
addCommand() {
const keyId = this.selectedPublicKey(),
option = keyId ? this.publicKeysOptions().find(item => item && keyId === item.id) : null;
if (option) {
this.encryptKeys.push({
empty: !option.key,
selected: ko.observable(!!option.key),
removable: ko.observable(!this.sign() || !this.signKey() || this.signKey().key.id !== option.key.id),
users: option.key.users,
hash: option.key.id.slice(KEY_NAME_SUBSTR).toUpperCase(),
key: option.key
});
}
}
updateCommand() {
this.encryptKeys.forEach(oKey =>
oKey.removable(!this.sign() || !this.signKey() || this.signKey().key.id !== oKey.key.id)
);
}
deletePublickKey(publicKey) {
this.encryptKeys.remove(publicKey);
}
clearPopup() {
this.notification('');
this.sign(false);
this.encrypt(false);
this.password('');
this.signKey(null);
this.encryptKeys([]);
this.text('');
this.resultCallback = null;
}
onBuild() {
// shortcuts.add('tab', 'shift', Scope.ComposeOpenPgp, () => {
shortcuts.add('tab', '', Scope.ComposeOpenPgp, () => {
let btn = this.querySelector('.inputPassword');
if (btn.matches(':focus')) {
btn = this.querySelector('.buttonDo');
}
btn.focus();
return false;
});
}
onHideWithDelay() {
this.clearPopup();
}
onShowWithDelay() {
this.querySelector(this.sign() ? '.inputPassword' : '.buttonDo').focus();
}
onShow(fCallback, sText, identity, sTo, sCc, sBcc) {
this.clearPopup();
let rec = [],
emailLine = '';
const email = new EmailModel();
this.resultCallback = fCallback;
if (sTo) {
rec.push(sTo);
}
if (sCc) {
rec.push(sCc);
}
if (sBcc) {
rec.push(sBcc);
}
rec = rec.join(', ').split(',');
rec = rec.map(value => {
email.clear();
email.parse(value.trim());
return email.email || false;
}).filter(v => v);
if (identity && identity.email()) {
emailLine = identity.email();
rec.unshift(emailLine);
const keys = PgpUserStore.findAllPrivateKeysByEmailNotNative(emailLine);
if (keys && keys[0]) {
this.signKey({
users: keys[0].users || [emailLine],
hash: keys[0].id.slice(KEY_NAME_SUBSTR).toUpperCase(),
key: keys[0]
});
}
}
if (this.signKey()) {
this.sign(true);
}
if (rec.length) {
this.encryptKeys(
rec.map(recEmail => {
const keys = PgpUserStore.findAllPublicKeysByEmailNotNative(recEmail);
return keys
? keys.map(publicKey => ({
empty: !publicKey,
selected: ko.observable(!!publicKey),
removable: ko.observable(
!this.sign() || !this.signKey() || this.signKey().key.id !== publicKey.id
),
users: publicKey ? publicKey.users || [recEmail] : [recEmail],
hash: publicKey ? publicKey.id.slice(KEY_NAME_SUBSTR).toUpperCase() : '',
key: publicKey
}))
: [];
}).flat().validUnique(encryptKey => encryptKey.hash)
);
if (this.encryptKeys.length) {
this.encrypt(true);
}
}
this.text(sText);
}
}
export { ComposeOpenPgpPopupView, ComposeOpenPgpPopupView as default };

View file

@ -47,7 +47,7 @@ class NewOpenPgpKeyPopupView extends AbstractViewPopup {
setTimeout(() => {
try {
PgpUserStore.openpgp
openpgp
.generateKey({
userIds: [userId],
numBits: pInt(this.keyBitLength()),
@ -62,7 +62,7 @@ class NewOpenPgpKeyPopupView extends AbstractViewPopup {
openpgpKeyring.store();
rl.app.reloadOpenPgpKeys();
PgpUserStore.reloadOpenPgpKeys();
this.cancelCommand();
}
})

View file

@ -180,7 +180,7 @@ export class MailMessageView extends AbstractViewRight {
pgpSigned: () => MessageUserStore.message() && !!MessageUserStore.message().pgpSigned(),
pgpEncrypted: () => PgpUserStore.openpgp
pgpEncrypted: () => PgpUserStore.isSupported()
&& MessageUserStore.message() && MessageUserStore.message().isPgpEncrypted(),
messageListOrViewLoading: