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

@ -4,7 +4,6 @@ import { isArray, arrayLength, pString, forEachObjectValue } from 'Common/Utils'
import { delegateRunOnDestroy, mailToHelper, setLayoutResizer } from 'Common/UtilsUser';
import {
Capa,
Notification,
Scope
} from 'Common/Enums';
@ -36,11 +35,7 @@ import {
getFolderFromCacheList
} from 'Common/Cache';
import {
mailBox,
openPgpWorkerJs,
openPgpJs
} from 'Common/Links';
import { mailBox } from 'Common/Links';
import { getNotification, i18n } from 'Common/Translator';
@ -61,7 +56,6 @@ import Remote from 'Remote/User/Fetch';
import { EmailModel } from 'Model/Email';
import { AccountModel } from 'Model/Account';
import { IdentityModel } from 'Model/Identity';
import { OpenPgpKeyModel } from 'Model/OpenPgpKey';
import { LoginUserScreen } from 'Screen/User/Login';
import { MailBoxUserScreen } from 'Screen/User/MailBox';
@ -346,66 +340,6 @@ class AppUser extends AbstractApp {
return false;
}
reloadOpenPgpKeys() {
if (PgpUserStore.openpgp) {
const keys = [],
email = new EmailModel(),
openpgpKeyring = PgpUserStore.openpgpKeyring,
openpgpKeys = openpgpKeyring ? openpgpKeyring.getAllKeys() : [];
openpgpKeys.forEach((oItem, iIndex) => {
if (oItem && oItem.primaryKey) {
const aEmails = [],
aUsers = [],
primaryUser = oItem.getPrimaryUser(),
user =
primaryUser && primaryUser.user
? primaryUser.user.userId.userid
: oItem.users && oItem.users[0]
? oItem.users[0].userId.userid
: '';
if (oItem.users) {
oItem.users.forEach(item => {
if (item.userId) {
email.clear();
email.parse(item.userId.userid);
if (email.validate()) {
aEmails.push(email.email);
aUsers.push(item.userId.userid);
}
}
});
}
if (aEmails.length) {
keys.push(
new OpenPgpKeyModel(
iIndex,
oItem.primaryKey.getFingerprint(),
oItem.primaryKey
.getKeyId()
.toHex()
.toLowerCase(),
oItem.getKeyIds()
.map(item => (item && item.toHex ? item.toHex() : null))
.validUnique(),
aUsers,
aEmails,
oItem.isPrivate(),
oItem.armor(),
user
)
);
}
}
});
delegateRunOnDestroy(PgpUserStore.openpgpkeys());
PgpUserStore.openpgpkeys(keys);
}
}
accountsAndIdentities() {
AccountUserStore.loading(true);
IdentityUserStore.loading(true);
@ -772,23 +706,7 @@ class AppUser extends AbstractApp {
setInterval(this.reloadTime(), 60000);
if (window.crypto && crypto.getRandomValues && Settings.capa(Capa.OpenPGP)) {
const script = createElement('script', {src:openPgpJs()});
script.onload = () => {
PgpUserStore.openpgp = openpgp;
if (window.Worker) {
try {
PgpUserStore.openpgp.initWorker({ path: openPgpWorkerJs() });
} catch (e) {
console.error(e);
}
}
PgpUserStore.openpgpKeyring = new openpgp.Keyring();
this.reloadOpenPgpKeys();
};
script.onerror = () => console.error(script.src);
doc.head.append(script);
}
PgpUserStore.init();
} else {
this.logout();
}

View file

@ -648,7 +648,7 @@ export class MessageModel extends AbstractModel {
// const message = self.message();
// message && pgpClickHelper(message.body, message.plain(), message.getEmails(['from', 'to', 'cc']));
/*
pgpEncrypted: () => PgpUserStore.openpgp
pgpEncrypted: () => PgpUserStore.isSupported()
&& MessageUserStore.message() && MessageUserStore.message().isPgpEncrypted(),
*/
}
@ -660,9 +660,11 @@ export class MessageModel extends AbstractModel {
params.Uid = this.uid;
rl.app.Remote.post('MessagePgpVerify', null, params)
.then(data => {
// TODO
console.dir(data);
})
.catch(error => {
// TODO
console.dir(error);
});
}

View file

@ -2,7 +2,6 @@ import ko from 'ko';
import { arrayLength } from 'Common/Utils';
import { AbstractModel } from 'Knoin/AbstractModel';
import { PgpUserStore } from 'Stores/User/Pgp';
export class OpenPgpKeyModel extends AbstractModel {
/**
@ -35,25 +34,21 @@ export class OpenPgpKeyModel extends AbstractModel {
this.deleteAccess = ko.observable(false);
}
getNativeKey() {
let key = null;
/**
* OpenPGP.js
*/
getNativeKeys() {
try {
key = PgpUserStore.openpgp.key.readArmored(this.armor);
let key = openpgp.key.readArmored(this.armor);
if (key && !key.err && key.keys && key.keys[0]) {
return key;
return key.keys;
}
} catch (e) {
console.log(e);
console.error(e);
}
return null;
}
getNativeKeys() {
const key = this.getNativeKey();
return key && key.keys ? key.keys : null;
}
select(pattern, property) {
if (this[property]) {
const index = this[property].indexOf(pattern);

View file

@ -59,7 +59,7 @@ export class OpenPgpUserSettings /*extends AbstractViewSettings*/ {
PgpUserStore.openpgpKeyring.store();
}
rl.app.reloadOpenPgpKeys();
PgpUserStore.reloadOpenPgpKeys();
}
}
}

View file

@ -1,43 +1,169 @@
import ko from 'ko';
import { Capa } from 'Common/Enums';
import { doc, createElement, Settings } from 'Common/Globals';
import { openPgpJs, openPgpWorkerJs } from 'Common/Links';
import { isArray, arrayLength, pString, addComputablesTo } from 'Common/Utils';
import { AccountUserStore } from 'Stores/User/Account';
import { delegateRunOnDestroy } from 'Common/UtilsUser';
import { showScreenPopup } from 'Knoin/Knoin';
import { MessageOpenPgpPopupView } from 'View/Popup/MessageOpenPgp';
import { EmailModel } from 'Model/Email';
import { OpenPgpKeyModel } from 'Model/OpenPgpKey';
import Remote from 'Remote/User/Fetch';
const
findKeyByHex = (keys, hash, isPrivate) =>
keys.find(item => item && isPrivate == item.isPrivate && (hash === item.id || item.ids.includes(hash))),
findAllKeysByEmail = (keys, email, isPrivate) =>
keys.filter(item => item && isPrivate == item.isPrivate && item.emails.includes(email));
export const PgpUserStore = new class {
constructor() {
this.openpgp = null;
// PECL gnupg / PEAR Crypt_GPG
this.gnupgkeys;
// OpenPGP.js
this.openpgpkeys = ko.observableArray();
this.openpgpKeyring = null;
// https://mailvelope.github.io/mailvelope/Keyring.html
this.mailvelopeKeyring = null;
addComputablesTo(this, {
openpgpkeysPublic: () => this.openpgpkeys.filter(item => item && !item.isPrivate),
openpgpkeysPrivate: () => this.openpgpkeys.filter(item => item && item.isPrivate)
});
}
init() {
if (Settings.capa(Capa.OpenPGP)) {
if (window.crypto && crypto.getRandomValues) {
const script = createElement('script', {src:openPgpJs()});
script.onload = () => {
if (window.Worker) {
try {
openpgp.initWorker({ path: openPgpWorkerJs() });
} catch (e) {
console.error(e);
}
}
this.loadKeyrings();
};
script.onerror = () => console.error(script.src);
doc.head.append(script);
} else {
this.loadKeyrings();
}
}
}
loadKeyrings(identifier) {
if (window.mailvelope) {
console.log('mailvelope ready');
var fn = keyring => this.mailvelopeKeyring = keyring;
mailvelope.getKeyring().then(fn, err => {
if (identifier) {
// attempt to create a new keyring for this app/user
mailvelope.createKeyring(identifier).then(fn, err => console.error(err));
} else {
console.error(err);
}
});
addEventListener('mailvelope-disconnect', event => {
alert('Mailvelope is updated to version ' + event.detail.version + '. Reload page');
}, false);
} else {
addEventListener('mailvelope', () => this.loadKeyrings(identifier));
}
if (openpgp) {
console.log('openpgp.js ready');
this.openpgpKeyring = new openpgp.Keyring();
this.reloadOpenPgpKeys();
}
this.gnupgkeys = [];
Remote.request('PgpGetKeysEmails',
(iError, oData) => {
console.dir(oData);
}
);
}
reloadOpenPgpKeys() {
if (this.openpgpKeyring) {
const keys = [],
email = new EmailModel();
this.openpgpKeyring.getAllKeys().forEach((oItem, iIndex) => {
if (oItem && oItem.primaryKey) {
const aEmails = [],
aUsers = [],
primaryUser = oItem.getPrimaryUser(),
user =
primaryUser && primaryUser.user
? primaryUser.user.userId.userid
: oItem.users && oItem.users[0]
? oItem.users[0].userId.userid
: '';
if (oItem.users) {
oItem.users.forEach(item => {
if (item.userId) {
email.clear();
email.parse(item.userId.userid);
if (email.validate()) {
aEmails.push(email.email);
aUsers.push(item.userId.userid);
}
}
});
}
if (aEmails.length) {
keys.push(
new OpenPgpKeyModel(
iIndex,
oItem.primaryKey.getFingerprint(),
oItem.primaryKey
.getKeyId()
.toHex()
.toLowerCase(),
oItem.getKeyIds()
.map(item => (item && item.toHex ? item.toHex() : null))
.validUnique(),
aUsers,
aEmails,
oItem.isPrivate(),
oItem.armor(),
user
)
);
}
}
});
delegateRunOnDestroy(this.openpgpkeys());
this.openpgpkeys(keys);
}
}
/**
* @returns {boolean}
*/
isSupported() {
return !!this.openpgp;
}
findKeyByHex(keys, hash) {
return keys.find(item => hash && item && (hash === item.id || item.ids.includes(hash)));
return !!(window.openpgp || window.mailvelope);
}
findPublicKeyByHex(hash) {
return this.findKeyByHex(this.openpgpkeysPublic(), hash);
return findKeyByHex(this.openpgpkeys, hash, 0);
}
findPrivateKeyByHex(hash) {
return this.findKeyByHex(this.openpgpkeysPrivate(), hash);
return findKeyByHex(this.openpgpkeys, hash, 1);
}
findPublicKeysByEmail(email) {
@ -77,19 +203,53 @@ export const PgpUserStore = new class {
}
/**
* @param {string} email
* @returns {?}
* Checks if verifying/encrypting a message is possible with given email addresses.
* Returns the first library that can.
*/
findPublicKeyByEmailNotNative(email) {
return this.openpgpkeysPublic().find(item => item && item.emails.includes(email)) || null;
async hasPublicKeyForEmails(recipients, all) {
const
count = recipients.length,
openpgp = count && this.openpgpkeys && recipients.filter(email =>
this.openpgpkeys.find(item => item && !item.isPrivate && item.emails.includes(email))
);
if (openpgp && openpgp.length && (!all || openpgp.length === count)) {
return 'openpgp';
}
let mailvelope = count && this.mailvelopeKeyring && await this.mailvelopeKeyring.validKeyForAddress(recipients)
/*.then(LookupResult => Object.entries(LookupResult))*/;
mailvelope = Object.entries(mailvelope);
if (mailvelope && mailvelope.length
&& (all ? (mailvelope.filter(([, value]) => value).length === count) : mailvelope.find(([, value]) => value))
) {
return 'mailvelope';
}
return false;
}
/**
* @param {string} email
* @returns {?}
* Checks if signing/decrypting a message is possible with given email address.
* Returns the first library that can.
*/
findPrivateKeyByEmailNotNative(email) {
return this.openpgpkeysPrivate().find(item => item && item.emails.includes(email)) || null;
async hasPrivateKeyForEmail(email) {
if (this.openpgpkeys && this.openpgpkeys.find(item => item && item.isPrivate && item.emails.includes(email))) {
return 'openpgp';
}
let keyring = this.mailvelopeKeyring;
if (keyring) {
/**
* Mailvelope can't find by email, so we must get the fingerprint and use that instead
*/
let keys = await keyring.validKeyForAddress([email]);
if (keys && keys[email] && await keyring.hasPrivateKey(keys[email].keys[0].fingerprint)) {
return 'mailvelope';
}
}
return false;
}
/**
@ -97,7 +257,7 @@ export const PgpUserStore = new class {
* @returns {?}
*/
findAllPublicKeysByEmailNotNative(email) {
return this.openpgpkeysPublic().filter(item => item && item.emails.includes(email)) || null;
return findAllKeysByEmail(this.openpgpkeys, email, 0);
}
/**
@ -105,7 +265,7 @@ export const PgpUserStore = new class {
* @returns {?}
*/
findAllPrivateKeysByEmailNotNative(email) {
return this.openpgpkeysPrivate().filter(item => item && item.emails.includes(email)) || null;
return findAllKeysByEmail(this.openpgpkeys, email, 1);
}
/**
@ -115,7 +275,7 @@ export const PgpUserStore = new class {
*/
findPrivateKeyByEmail(email, password) {
let privateKey = null;
const key = this.openpgpkeysPrivate().find(item => item && item.emails.includes(email));
const key = this.openpgpkeys.find(item => item && item.isPrivate && item.emails.includes(email));
if (key) {
try {
@ -131,14 +291,6 @@ export const PgpUserStore = new class {
return privateKey;
}
/**
* @param {string=} password
* @returns {?}
*/
findSelfPrivateKey(password) {
return this.findPrivateKeyByEmail(AccountUserStore.email(), password);
}
decryptMessage(message, recipients, fCallback) {
if (message && message.getEncryptionKeyIds) {
const privateKeys = this.findPrivateKeysByEncryptionKeyIds(message.getEncryptionKeyIds(), recipients, true);
@ -210,4 +362,81 @@ export const PgpUserStore = new class {
return false;
}
/**
* Creates an iframe to display the decrypted content of the encrypted mail.
* The iframe will be injected into the container identified by selector.
*/
/*
mailvelope.createDisplayContainer(selector, armored, this.mailvelopeKeyring, {senderAddress:''}).then(status => {
if (status.error && status.error.message) {
return error_handler(status.error);
}
ref.hide_message(msgid);
$(selector).children().not('iframe').hide();
$(ref.gui_objects.messagebody).addClass('mailvelope');
// on success we can remove encrypted part from the attachments list
if (ref.env.pgp_mime_part)
$('#attach' + ref.env.pgp_mime_part).remove();
setTimeout(function() { $(window).resize(); }, 10);
}, error_handler);
*/
/**
* Creates an iframe with an editor for a new encrypted mail.
* The iframe will be injected into the container identified by selector.
* https://mailvelope.github.io/mailvelope/Editor.html
*/
/*
mailvelope.createEditorContainer(selector, this.mailvelopeKeyring, {
quota: 20480, // mail content (text + attachments) limit in kilobytes (default: 20480)
signMsg: false, // if true then the mail will be signed (default: false)
armoredDraft: '', // Ascii Armored PGP Text Block
a PGP message, signed and encrypted with the default key of the user, will be used to restore a draft in the editor
The armoredDraft parameter can't be combined with the parameters: predefinedText, quotedMail... parameters, keepAttachments
predefinedText: '', // text that will be added to the editor
quotedMail: '', // Ascii Armored PGP Text Block mail that should be quoted
quotedMailIndent: true, // if true the quoted mail will be indented (default: true)
quotedMailHeader: '', // header to be added before the quoted mail
keepAttachments: false, // add attachments of quotedMail to editor (default: false)
}).then(editor => {
editor.editorId;
}, error_handler)
*/
/**
* Creates an iframe to display the keyring settings.
* The iframe will be injected into the container identified by selector.
*/
/*
mailvelope.createSettingsContainer(selector [, keyring], options)
*/
/**
* Returns headers that should be added to an outgoing email.
* So far this is only the autocrypt header.
*/
/*
this.mailvelopeKeyring.additionalHeadersForOutgoingEmail(headers)
*/
/*
this.mailvelopeKeyring.addSyncHandler(syncHandlerObj)
*/
/*
this.mailvelopeKeyring.createKeyGenContainer(selector, {
// userIds: [],
keySize: 4096
})
*/
/*
exportOwnPublicKey(emailAddr).then(<AsciiArmored, Error>)
this.mailvelopeKeyring.hasPrivateKey(fingerprint)
this.mailvelopeKeyring.importPublicKey(armored)
*/
};

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: