import ko from 'ko'; import _ from '_'; import $ from '$'; import {i18n} from 'Common/Translator'; import {log, isArray, isNonEmptyArray, pString, isUnd, trim} from 'Common/Utils'; import AccountStore from 'Stores/User/Account'; import {showScreenPopup} from 'Knoin/Knoin'; class PgpUserStore { constructor() { this.capaOpenPGP = ko.observable(false); this.openpgp = null; this.openpgpkeys = ko.observableArray([]); this.openpgpKeyring = null; this.openpgpkeysPublic = this.openpgpkeys.filter((item) => !!(item && !item.isPrivate)); this.openpgpkeysPrivate = this.openpgpkeys.filter((item) => !!(item && item.isPrivate)); } /** * @returns {boolean} */ isSupported() { return !!this.openpgp; } findKeyByHex(keys, hash) { return _.find(keys, (item) => (hash && item && (hash === item.id || -1 < item.ids.indexOf(hash)))); } findPublicKeyByHex(hash) { return this.findKeyByHex(this.openpgpkeysPublic(), hash); } findPrivateKeyByHex(hash) { return this.findKeyByHex(this.openpgpkeysPrivate(), hash); } findPublicKeysByEmail(email) { return _.compact(_.flatten(_.map(this.openpgpkeysPublic(), (item) => { const key = item && -1 < item.emails.indexOf(email) ? item : null; return key ? key.getNativeKeys() : [null]; }), true)); } findPublicKeysBySigningKeyIds(signingKeyIds) { return _.compact(_.flatten(_.map(signingKeyIds, (id) => { const key = id && id.toHex ? this.findPublicKeyByHex(id.toHex()) : null; return key ? key.getNativeKeys() : [null]; }), true)); } findPrivateKeysByEncryptionKeyIds(encryptionKeyIds, recipients, returnWrapKeys) { let result = isArray(encryptionKeyIds) ? _.compact(_.flatten(_.map(encryptionKeyIds, (id) => { const key = id && id.toHex ? this.findPrivateKeyByHex(id.toHex()) : null; return key ? (returnWrapKeys ? [key] : key.getNativeKeys()) : [null]; }), true)) : []; if (0 === result.length && isNonEmptyArray(recipients)) { result = _.uniq(_.compact(_.flatten(_.map(recipients, (sEmail) => { const keys = sEmail ? this.findAllPrivateKeysByEmailNotNative(sEmail) : null; return keys ? (returnWrapKeys ? keys : _.flatten(_.map(keys, (key) => key.getNativeKeys()), true)) : [null]; }), true)), (key) => key.id); } return result; } /** * @param {string} email * @returns {?} */ findPublicKeyByEmailNotNative(email) { return _.find(this.openpgpkeysPublic(), (item) => (item && -1 < item.emails.indexOf(email))) || null; } /** * @param {string} email * @returns {?} */ findPrivateKeyByEmailNotNative(email) { return _.find(this.openpgpkeysPrivate(), (item) => (item && -1 < item.emails.indexOf(email))) || null; } /** * @param {string} email * @returns {?} */ findAllPublicKeysByEmailNotNative(email) { return _.filter(this.openpgpkeysPublic(), (item) => (item && -1 < item.emails.indexOf(email))) || null; } /** * @param {string} email * @returns {?} */ findAllPrivateKeysByEmailNotNative(email) { return _.filter(this.openpgpkeysPrivate(), (item) => (item && -1 < item.emails.indexOf(email))) || null; } /** * @param {string} email * @param {string=} password * @returns {?} */ findPrivateKeyByEmail(email, password) { let privateKey = null; const key = _.find(this.openpgpkeysPrivate(), (item) => (item && -1 < item.emails.indexOf(email))); if (key) { try { privateKey = key.getNativeKeys()[0] || null; if (privateKey) { privateKey.decrypt(pString(password)); } } catch (e) { privateKey = null; } } return privateKey; } /** * @param {string=} password * @returns {?} */ findSelfPrivateKey(password) { return this.findPrivateKeyByEmail(AccountStore.email(), password); } decryptMessage(message, recipients, fCallback) { if (message && message.getEncryptionKeyIds) { const privateKeys = this.findPrivateKeysByEncryptionKeyIds(message.getEncryptionKeyIds(), recipients, true); if (privateKeys && 0 < privateKeys.length) { showScreenPopup(require('View/Popup/MessageOpenPgp'), [(decryptedKey) => { if (decryptedKey) { message.decrypt(decryptedKey).then((decryptedMessage) => { let privateKey = null; if (decryptedMessage) { privateKey = this.findPrivateKeyByHex(decryptedKey.primaryKey.keyid.toHex()); if (privateKey) { this.verifyMessage(decryptedMessage, (oValidKey, aSigningKeyIds) => { fCallback(privateKey, decryptedMessage, oValidKey || null, aSigningKeyIds || null); }); } else { fCallback(privateKey, decryptedMessage); } } else { fCallback(privateKey, decryptedMessage); } }, () => { fCallback(null, null); }); } else { fCallback(null, null); } }, privateKeys]); return false; } } fCallback(null, null); return false; } verifyMessage(message, fCallback) { if (message && message.getSigningKeyIds) { const signingKeyIds = message.getSigningKeyIds(); if (signingKeyIds && 0 < signingKeyIds.length) { const publicKeys = this.findPublicKeysBySigningKeyIds(signingKeyIds); if (publicKeys && 0 < publicKeys.length) { try { const result = message.verify(publicKeys), valid = _.find(_.isArray(result) ? result : [], (item) => (item && item.valid && item.keyid)); if (valid && valid.keyid && valid.keyid && valid.keyid.toHex) { fCallback(this.findPublicKeyByHex(valid.keyid.toHex())); return true; } } catch (e) { log(e); } } fCallback(null, signingKeyIds); return false; } } fCallback(null); return false; } controlsHelper(dom, verControl, success, title, text) { if (success) { dom.removeClass('error').addClass('success').attr('title', title); verControl.removeClass('error').addClass('success').attr('title', title); } else { dom.removeClass('success').addClass('error').attr('title', title); verControl.removeClass('success').addClass('error').attr('title', title); } if (!isUnd(text)) { dom.text(trim(text)); } } static domControlEncryptedClickHelper(store, dom, armoredMessage, recipients) { return function() { let message = null; const $this = $(this); // eslint-disable-line no-invalid-this if ($this.hasClass('success')) { return false; } try { message = store.openpgp.message.readArmored(armoredMessage); } catch (e) { log(e); } if (message && message.getText && message.verify && message.decrypt) { store.decryptMessage(message, recipients, (validPrivateKey, decryptedMessage, validPublicKey, signingKeyIds) => { if (decryptedMessage) { if (validPublicKey) { store.controlsHelper(dom, $this, true, i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', { 'USER': validPublicKey.user + ' (' + validPublicKey.id + ')' }), decryptedMessage.getText()); } else if (validPrivateKey) { const keyIds = isNonEmptyArray(signingKeyIds) ? signingKeyIds : null, additional = keyIds ? _.compact(_.map(keyIds, (item) => (item && item.toHex ? item.toHex() : null))).join(', ') : ''; store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : ''), decryptedMessage.getText()); } else { store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR')); } } else { store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR')); } }); return false; } store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR')); return false; }; } static domControlSignedClickHelper(store, dom, armoredMessage) { return function() { let message = null; const $this = $(this); // eslint-disable-line no-invalid-this if ($this.hasClass('success') || $this.hasClass('error')) { return false; } try { message = store.openpgp.cleartext.readArmored(armoredMessage); } catch (e) { log(e); } if (message && message.getText && message.verify) { store.verifyMessage(message, (validKey, signingKeyIds) => { if (validKey) { store.controlsHelper(dom, $this, true, i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', { 'USER': validKey.user + ' (' + validKey.id + ')' }), message.getText()); } else { const keyIds = isNonEmptyArray(signingKeyIds) ? signingKeyIds : null, additional = keyIds ? _.compact(_.map(keyIds, (item) => (item && item.toHex ? item.toHex() : null))).join(', ') : ''; store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '')); } }); return false; } store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR')); return false; }; } /** * @param {*} dom * @param {MessageModel} rainLoopMessage */ initMessageBodyControls(dom, rainLoopMessage) { if (dom && !dom.hasClass('inited')) { dom.addClass('inited'); const encrypted = dom.hasClass('encrypted'), signed = dom.hasClass('signed'), recipients = rainLoopMessage ? rainLoopMessage.getEmails(['from', 'to', 'cc']) : []; let verControl = null; if (encrypted || signed) { const domText = dom.text(); dom.data('openpgp-original', domText); if (encrypted) { verControl = $('
') .attr('title', i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC')) .on('click', PgpUserStore.domControlEncryptedClickHelper(this, dom, domText, recipients)); } else if (signed) { verControl = $('
') .attr('title', i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC')) .on('click', PgpUserStore.domControlSignedClickHelper(this, dom, domText)); } if (verControl) { dom.before(verControl).before('
'); } } } } } export default new PgpUserStore();