mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-31 04:59:21 +03:00
Revamp PGP management due to implementing Mailvelop and PEAR Crypt_GPG
This commit is contained in:
parent
43a1196dbb
commit
a47397ef09
16 changed files with 885 additions and 633 deletions
|
|
@ -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)
|
||||
*/
|
||||
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue