snappymail/dev/Stores/User/Pgp.js

414 lines
10 KiB
JavaScript
Raw Normal View History

import ko from 'ko';
import _ from '_';
import $ from '$';
2016-06-30 08:02:45 +08:00
2019-07-05 03:19:24 +08:00
import { i18n } from 'Common/Translator';
import { log, isArray, isNonEmptyArray, pString, isUnd, trim } from 'Common/Utils';
2016-06-30 08:02:45 +08:00
import AccountStore from 'Stores/User/Account';
2019-07-05 03:19:24 +08:00
import { showScreenPopup } from 'Knoin/Knoin';
2019-07-05 03:19:24 +08:00
class PgpUserStore {
constructor() {
this.capaOpenPGP = ko.observable(false);
2016-06-30 08:02:45 +08:00
this.openpgp = null;
2016-06-30 08:02:45 +08:00
this.openpgpkeys = ko.observableArray([]);
this.openpgpKeyring = null;
2016-06-30 08:02:45 +08:00
this.openpgpkeysPublic = ko.computed(() => this.openpgpkeys().filter(item => !!(item && !item.isPrivate)));
this.openpgpkeysPrivate = ko.computed(() => this.openpgpkeys().filter(item => !!(item && item.isPrivate)));
}
2016-06-30 08:02:45 +08:00
/**
* @returns {boolean}
*/
isSupported() {
return !!this.openpgp;
}
2016-06-30 08:02:45 +08:00
findKeyByHex(keys, hash) {
2020-07-21 03:39:00 +08:00
return _.find(keys, (item) => hash && item && (hash === item.id || item.ids.includes(hash)));
}
2016-06-30 08:02:45 +08:00
findPublicKeyByHex(hash) {
return this.findKeyByHex(this.openpgpkeysPublic(), hash);
}
2016-06-30 08:02:45 +08:00
findPrivateKeyByHex(hash) {
return this.findKeyByHex(this.openpgpkeysPrivate(), hash);
}
2016-06-30 08:02:45 +08:00
findPublicKeysByEmail(email) {
2019-07-05 03:19:24 +08:00
return _.compact(
_.flatten(
_.map(this.openpgpkeysPublic(), (item) => {
2020-07-21 03:39:00 +08:00
const key = item && item.emails.includes(email) ? item : null;
2019-07-05 03:19:24 +08:00
return key ? key.getNativeKeys() : [null];
}),
true
)
);
}
2016-06-30 08:02:45 +08:00
findPublicKeysBySigningKeyIds(signingKeyIds) {
2019-07-05 03:19:24 +08:00
return _.compact(
_.flatten(
_.map(signingKeyIds, (id) => {
const key = id && id.toHex ? this.findPublicKeyByHex(id.toHex()) : null;
return key ? key.getNativeKeys() : [null];
}),
true
)
);
}
2016-06-30 08:02:45 +08:00
findPrivateKeysByEncryptionKeyIds(encryptionKeyIds, recipients, returnWrapKeys) {
2019-07-05 03:19:24 +08:00
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
2019-12-25 03:05:46 +08:00
: _.flatten(
_.map(keys, (key) => key.getNativeKeys()),
true
)
2019-07-05 03:19:24 +08:00
: [null];
}),
true
)
),
(key) => key.id
);
}
return result;
2015-02-03 07:58:58 +08:00
}
2015-02-01 23:44:44 +08:00
/**
* @param {string} email
* @returns {?}
*/
findPublicKeyByEmailNotNative(email) {
2020-07-21 03:39:00 +08:00
return _.find(this.openpgpkeysPublic(), (item) => item && item.emails.includes(email)) || null;
}
2016-06-30 08:02:45 +08:00
/**
* @param {string} email
* @returns {?}
*/
findPrivateKeyByEmailNotNative(email) {
2020-07-21 03:39:00 +08:00
return _.find(this.openpgpkeysPrivate(), (item) => item && item.emails.includes(email)) || null;
}
/**
* @param {string} email
* @returns {?}
*/
findAllPublicKeysByEmailNotNative(email) {
2020-07-21 03:39:00 +08:00
return this.openpgpkeysPublic().filter(item => item && item.emails.includes(email)) || null;
}
/**
* @param {string} email
* @returns {?}
*/
findAllPrivateKeysByEmailNotNative(email) {
2020-07-21 03:39:00 +08:00
return this.openpgpkeysPrivate().filter(item => item && item.emails.includes(email)) || null;
}
/**
* @param {string} email
* @param {string=} password
* @returns {?}
*/
findPrivateKeyByEmail(email, password) {
let privateKey = null;
2020-07-21 03:39:00 +08:00
const key = _.find(this.openpgpkeysPrivate(), (item) => item && item.emails.includes(email));
2019-07-05 03:19:24 +08:00
if (key) {
try {
privateKey = key.getNativeKeys()[0] || null;
2019-07-05 03:19:24 +08:00
if (privateKey) {
privateKey.decrypt(pString(password));
}
2019-07-05 03:19:24 +08:00
} catch (e) {
privateKey = null;
2015-02-22 06:00:51 +08:00
}
}
2016-06-30 08:02:45 +08:00
return privateKey;
}
2016-06-30 08:02:45 +08:00
/**
* @param {string=} password
* @returns {?}
*/
findSelfPrivateKey(password) {
return this.findPrivateKeyByEmail(AccountStore.email(), password);
}
2016-08-10 02:58:34 +08:00
decryptMessage(message, recipients, fCallback) {
2019-07-05 03:19:24 +08:00
if (message && message.getEncryptionKeyIds) {
const privateKeys = this.findPrivateKeysByEncryptionKeyIds(message.getEncryptionKeyIds(), recipients, true);
2019-07-05 03:19:24 +08:00
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);
}
2019-07-05 03:19:24 +08:00
);
} else {
fCallback(null, null);
2019-07-05 03:19:24 +08:00
}
},
privateKeys
]);
return false;
}
}
fCallback(null, null);
return false;
2016-06-30 08:02:45 +08:00
}
2016-05-24 01:33:01 +08:00
verifyMessage(message, fCallback) {
2019-07-05 03:19:24 +08:00
if (message && message.getSigningKeyIds) {
const signingKeyIds = message.getSigningKeyIds();
2019-07-05 03:19:24 +08:00
if (signingKeyIds && 0 < signingKeyIds.length) {
const publicKeys = this.findPublicKeysBySigningKeyIds(signingKeyIds);
2019-07-05 03:19:24 +08:00
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;
}
2019-07-05 03:19:24 +08:00
} catch (e) {
log(e);
2015-10-14 00:36:43 +08:00
}
}
fCallback(null, signingKeyIds);
return false;
}
}
fCallback(null);
return false;
2016-06-30 08:02:45 +08:00
}
controlsHelper(dom, verControl, success, title, text) {
2019-07-05 03:19:24 +08:00
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);
2016-06-30 08:02:45 +08:00
}
2019-07-05 03:19:24 +08:00
if (!isUnd(text)) {
dom.text(trim(text));
2016-06-30 08:02:45 +08:00
}
}
static domControlEncryptedClickHelper(store, dom, armoredMessage, recipients) {
return function() {
let message = null;
const $this = $(this); // eslint-disable-line no-invalid-this
2019-07-05 03:19:24 +08:00
if ($this.hasClass('success')) {
return false;
}
2019-07-05 03:19:24 +08:00
try {
message = store.openpgp.message.readArmored(armoredMessage);
2019-07-05 03:19:24 +08:00
} catch (e) {
log(e);
}
2019-07-05 03:19:24 +08:00
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'));
}
}
2019-07-05 03:19:24 +08:00
);
return false;
}
store.controlsHelper(dom, $this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
return false;
};
}
2016-06-30 08:02:45 +08:00
static domControlSignedClickHelper(store, dom, armoredMessage) {
return function() {
let message = null;
const $this = $(this); // eslint-disable-line no-invalid-this
2016-07-16 05:29:42 +08:00
2019-07-05 03:19:24 +08:00
if ($this.hasClass('success') || $this.hasClass('error')) {
return false;
}
2019-07-05 03:19:24 +08:00
try {
message = store.openpgp.cleartext.readArmored(armoredMessage);
2019-07-05 03:19:24 +08:00
} catch (e) {
log(e);
}
2019-07-05 03:19:24 +08:00
if (message && message.getText && message.verify) {
store.verifyMessage(message, (validKey, signingKeyIds) => {
2019-07-05 03:19:24 +08:00
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'));
2016-06-30 08:02:45 +08:00
return false;
};
}
2016-06-30 08:02:45 +08:00
/**
* @param {*} dom
* @param {MessageModel} rainLoopMessage
*/
initMessageBodyControls(dom, rainLoopMessage) {
2019-07-05 03:19:24 +08:00
if (dom && !dom.hasClass('inited')) {
dom.addClass('inited');
2015-02-01 23:44:44 +08:00
2019-07-05 03:19:24 +08:00
const encrypted = dom.hasClass('encrypted'),
signed = dom.hasClass('signed'),
recipients = rainLoopMessage ? rainLoopMessage.getEmails(['from', 'to', 'cc']) : [];
2016-06-30 08:02:45 +08:00
let verControl = null;
2019-07-05 03:19:24 +08:00
if (encrypted || signed) {
const domText = dom.text();
dom.data('openpgp-original', domText);
2019-07-05 03:19:24 +08:00
if (encrypted) {
verControl = $('<div class="b-openpgp-control"><i class="icon-lock"></i></div>')
.attr('title', i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC'))
.on('click', PgpUserStore.domControlEncryptedClickHelper(this, dom, domText, recipients));
2019-07-05 03:19:24 +08:00
} else if (signed) {
verControl = $('<div class="b-openpgp-control"><i class="icon-lock"></i></div>')
.attr('title', i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC'))
.on('click', PgpUserStore.domControlSignedClickHelper(this, dom, domText));
}
2019-07-05 03:19:24 +08:00
if (verControl) {
dom.before(verControl).before('<div></div>');
}
2016-06-30 08:02:45 +08:00
}
}
}
}
2015-02-01 23:44:44 +08:00
export default new PgpUserStore();