mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-05 23:47:03 +03:00
#89 Improved handling of OpenPGP.js stored keys
This commit is contained in:
parent
ff7e41ad08
commit
9ab72e7a34
6 changed files with 73 additions and 151 deletions
23
README.md
23
README.md
|
|
@ -158,11 +158,6 @@ RainLoop 1.15 vs SnappyMail
|
||||||
|
|
||||||
For a user its around 70% smaller and faster than traditional RainLoop.
|
For a user its around 70% smaller and faster than traditional RainLoop.
|
||||||
|
|
||||||
|OpenPGP |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
|
||||||
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|
|
||||||
|openpgp.min.js | 330.742 | 293.972 |102.388 | 93.030 | 84.241 | 77.142 |
|
|
||||||
|openpgp.worker | 1.499 | 1.125 | 824 | 567 | 695 | 467 |
|
|
||||||
|
|
||||||
### CSS changes
|
### CSS changes
|
||||||
|
|
||||||
* Solve jQuery removed "features" with native css code
|
* Solve jQuery removed "features" with native css code
|
||||||
|
|
@ -178,13 +173,7 @@ For a user its around 70% smaller and faster than traditional RainLoop.
|
||||||
* Removed Internet Explorer from normalize.css
|
* Removed Internet Explorer from normalize.css
|
||||||
* Removed node_modules/opentip/css/opentip.css
|
* Removed node_modules/opentip/css/opentip.css
|
||||||
* Removed node_modules/pikaday/css/pikaday.css
|
* Removed node_modules/pikaday/css/pikaday.css
|
||||||
* Removed vendors/bootstrap/less/breadcrumbs.less
|
* Removed unused vendors/bootstrap/less/*
|
||||||
* Removed vendors/bootstrap/less/navbar.less
|
|
||||||
* Removed vendors/bootstrap/less/popovers.less
|
|
||||||
* Removed vendors/bootstrap/less/progress-bars.less
|
|
||||||
* Removed vendors/bootstrap/less/scaffolding.less
|
|
||||||
* Removed vendors/bootstrap/less/sprites.less
|
|
||||||
* Removed vendors/bootstrap/less/tooltip.less
|
|
||||||
* Removed vendors/jquery-nanoscroller/nanoscroller.css
|
* Removed vendors/jquery-nanoscroller/nanoscroller.css
|
||||||
* Removed vendors/jquery-letterfx/jquery-letterfx.min.css
|
* Removed vendors/jquery-letterfx/jquery-letterfx.min.css
|
||||||
* Removed vendors/Progress.js/minified/progressjs.min.css
|
* Removed vendors/Progress.js/minified/progressjs.min.css
|
||||||
|
|
@ -200,6 +189,16 @@ For a user its around 70% smaller and faster than traditional RainLoop.
|
||||||
|admin.css | | 30.558 | | 6.894 | 5.979 |
|
|admin.css | | 30.558 | | 6.894 | 5.979 |
|
||||||
|admin.min.css | | 24.546 | | 6.241 | 5.489 |
|
|admin.min.css | | 24.546 | | 6.241 | 5.489 |
|
||||||
|
|
||||||
|
### PGP
|
||||||
|
RainLoop uses the old OpenPGP.js v2
|
||||||
|
SnappyMail v2.12 uses OpenPGP.js v5, GnuPG and Mailvelope.
|
||||||
|
SnappyMail is able to use and generate ECDSA and EDDSA keys, where RainLoop does not.
|
||||||
|
|
||||||
|
|OpenPGP |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
||||||
|
|--------------- |--------: |--------: |------: |-------: |--------: |--------: |
|
||||||
|
|openpgp.min.js | 330.742 | 550.724 |102.388 | 170.033 | 84.241 | 139.429 |
|
||||||
|
|openpgp.worker | 1.499 | | 824 | | 695 | |
|
||||||
|
|
||||||
|
|
||||||
### Squire vs CKEditor
|
### Squire vs CKEditor
|
||||||
The [Squire](https://github.com/neilj/Squire) implementation is not 100% compatible yet, but it shows the massive overhead of CKEditor.
|
The [Squire](https://github.com/neilj/Squire) implementation is not 100% compatible yet, but it shows the massive overhead of CKEditor.
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import ko from 'ko';
|
|
||||||
|
|
||||||
import { arrayLength } from 'Common/Utils';
|
|
||||||
import { AbstractModel } from 'Knoin/AbstractModel';
|
|
||||||
|
|
||||||
export class OpenPgpKeyModel extends AbstractModel {
|
|
||||||
/**
|
|
||||||
* @param {string} guID
|
|
||||||
* @param {string} ID
|
|
||||||
* @param {array} IDs
|
|
||||||
* @param {array} userIDs
|
|
||||||
* @param {array} emails
|
|
||||||
* @param {string} armor
|
|
||||||
* @param {string} userID
|
|
||||||
*/
|
|
||||||
constructor(guID, ID, IDs, userIDs, emails, armor, userID) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.id = ID;
|
|
||||||
this.ids = arrayLength(IDs) ? IDs : [ID];
|
|
||||||
this.guid = guID;
|
|
||||||
this.user = '';
|
|
||||||
this.users = userIDs;
|
|
||||||
this.email = '';
|
|
||||||
this.emails = emails;
|
|
||||||
this.armor = armor;
|
|
||||||
|
|
||||||
if (this.users) {
|
|
||||||
const index = this.users.indexOf(userID);
|
|
||||||
if (-1 !== index) {
|
|
||||||
this.user = this.users[index];
|
|
||||||
this.email = this.emails[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.deleteAccess = ko.observable(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OpenPGP.js
|
|
||||||
*/
|
|
||||||
getNativeKeys() {
|
|
||||||
try {
|
|
||||||
let key = openpgp.key.readArmored(this.armor);
|
|
||||||
if (key && !key.err && key.keys && key.keys[0]) {
|
|
||||||
return key.keys;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { showScreenPopup } from 'Knoin/Knoin';
|
||||||
|
|
||||||
import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport';
|
import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport';
|
||||||
import { OpenPgpGeneratePopupView } from 'View/Popup/OpenPgpGenerate';
|
import { OpenPgpGeneratePopupView } from 'View/Popup/OpenPgpGenerate';
|
||||||
import { ViewOpenPgpKeyPopupView } from 'View/Popup/ViewOpenPgpKey';
|
|
||||||
|
|
||||||
import { Capa } from 'Common/Enums';
|
import { Capa } from 'Common/Enums';
|
||||||
import { Settings } from 'Common/Globals';
|
import { Settings } from 'Common/Globals';
|
||||||
|
|
@ -39,12 +38,6 @@ export class OpenPgpUserSettings /*extends AbstractViewSettings*/ {
|
||||||
showScreenPopup(OpenPgpGeneratePopupView);
|
showScreenPopup(OpenPgpGeneratePopupView);
|
||||||
}
|
}
|
||||||
|
|
||||||
viewOpenPgpKey(openPgpKey) {
|
|
||||||
if (openPgpKey) {
|
|
||||||
showScreenPopup(ViewOpenPgpKeyPopupView, [openPgpKey]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onBuild() {
|
onBuild() {
|
||||||
/**
|
/**
|
||||||
* Create an iframe to display the Mailvelope keyring settings.
|
* Create an iframe to display the Mailvelope keyring settings.
|
||||||
|
|
@ -52,15 +45,4 @@ export class OpenPgpUserSettings /*extends AbstractViewSettings*/ {
|
||||||
*/
|
*/
|
||||||
window.mailvelope && mailvelope.createSettingsContainer('#mailvelope-settings'/*[, keyring], options*/);
|
window.mailvelope && mailvelope.createSettingsContainer('#mailvelope-settings'/*[, keyring], options*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {OpenPgpKeyModel} openPgpKeyToRemove
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
deleteOpenPgpKey(openPgpKeyToRemove) {
|
|
||||||
if (openPgpKeyToRemove && openPgpKeyToRemove.deleteAccess()) {
|
|
||||||
this.openPgpKeyForDeletion(null);
|
|
||||||
PgpUserStore.deleteKey(openPgpKeyToRemove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ import { delegateRunOnDestroy } from 'Common/UtilsUser';
|
||||||
|
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
|
import { showScreenPopup } from 'Knoin/Knoin';
|
||||||
|
import { ViewOpenPgpKeyPopupView } from 'View/Popup/ViewOpenPgpKey';
|
||||||
|
|
||||||
const
|
const
|
||||||
findKeyByHex = (keys, hash) =>
|
findKeyByHex = (keys, hash) =>
|
||||||
keys.find(item => item && (hash === item.id || item.ids.includes(hash)));
|
keys.find(item => item && (hash === item.id || item.ids.includes(hash)));
|
||||||
|
|
@ -35,44 +38,62 @@ const
|
||||||
while (i--) {
|
while (i--) {
|
||||||
key = await openpgp.readKey({armoredKey:armoredKeys[i]});
|
key = await openpgp.readKey({armoredKey:armoredKeys[i]});
|
||||||
if (!key.err) {
|
if (!key.err) {
|
||||||
const aEmails = [];
|
keys.push(new OpenPgpKeyModel(armoredKeys[i], key));
|
||||||
if (key.users) {
|
|
||||||
key.users.forEach(user => {
|
|
||||||
if (user.userID.email) {
|
|
||||||
aEmails.push(user.userID.email);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
keys.push({
|
|
||||||
id: key.getKeyID().toHex(),
|
|
||||||
fingerprint: key.getFingerprint(),
|
|
||||||
can_encrypt: !!key.getEncryptionKey(),
|
|
||||||
can_sign: !!key.getSigningKey(),
|
|
||||||
emails: aEmails,
|
|
||||||
armor: armoredKeys[i],
|
|
||||||
deleteAccess: ko.observable(false)
|
|
||||||
});
|
|
||||||
// key.getUserIDs()
|
|
||||||
// key.getPrimaryUser()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return keys;
|
return keys;
|
||||||
/*
|
|
||||||
},
|
|
||||||
storeKeys = async (itemname, keys) => {
|
|
||||||
let armoredKeys = [], i = arrayLength(keys);
|
|
||||||
if (i) {
|
|
||||||
while (i--) {
|
|
||||||
armoredKeys.push(await keys[i].armor());
|
|
||||||
}
|
|
||||||
storage.setItem(itemname, JSON.stringify(armoredKeys));
|
|
||||||
} else {
|
|
||||||
storage.removeItem(itemname);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class OpenPgpKeyModel {
|
||||||
|
constructor(armor, key) {
|
||||||
|
this.key = key;
|
||||||
|
const aEmails = [];
|
||||||
|
if (key.users) {
|
||||||
|
key.users.forEach(user => {
|
||||||
|
if (user.userID.email) {
|
||||||
|
aEmails.push(user.userID.email);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.id = key.getKeyID().toHex();
|
||||||
|
this.fingerprint = key.getFingerprint();
|
||||||
|
this.can_encrypt = !!key.getEncryptionKey();
|
||||||
|
this.can_sign = !!key.getSigningKey();
|
||||||
|
this.emails = aEmails;
|
||||||
|
this.armor = armor;
|
||||||
|
this.deleteAccess = ko.observable(false);
|
||||||
|
this.openForDeletion = ko.observable(null).deleteAccessHelper();
|
||||||
|
// key.getUserIDs()
|
||||||
|
// key.getPrimaryUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
view() {
|
||||||
|
showScreenPopup(ViewOpenPgpKeyPopupView, [this]);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
if (this.deleteAccess()) {
|
||||||
|
this.openPgpKeyForDeletion(null);
|
||||||
|
let armoredKeys = [], itemname = publicKeysItem;
|
||||||
|
if (this.key.isPrivate()) {
|
||||||
|
itemname = privateKeysItem;
|
||||||
|
PgpUserStore.openpgpPrivateKeys.remove(this);
|
||||||
|
armoredKeys = PgpUserStore.openpgpPrivateKeys.map(item => item.armor);
|
||||||
|
} else {
|
||||||
|
PgpUserStore.openpgpPublicKeys.remove(this);
|
||||||
|
armoredKeys = PgpUserStore.openpgpPublicKeys.map(item => item.armor);
|
||||||
|
}
|
||||||
|
delegateRunOnDestroy(this);
|
||||||
|
if (armoredKeys.length) {
|
||||||
|
storage.setItem(itemname, JSON.stringify(armoredKeys));
|
||||||
|
} else {
|
||||||
|
storage.removeItem(itemname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const PgpUserStore = new class {
|
export const PgpUserStore = new class {
|
||||||
constructor() {
|
constructor() {
|
||||||
/**
|
/**
|
||||||
|
|
@ -289,33 +310,6 @@ export const PgpUserStore = new class {
|
||||||
* OpenPGP.js
|
* OpenPGP.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {OpenPgpKeyModel} openPgpKeyToRemove
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
deleteKey(openPgpKeyToRemove) {
|
|
||||||
const openpgpKeyring = this.openpgpKeyring;
|
|
||||||
if (openPgpKeyToRemove && openPgpKeyToRemove.deleteAccess() && openpgpKeyring) {
|
|
||||||
let items = [
|
|
||||||
this.openpgpPrivateKeys.find(key => openPgpKeyToRemove === key),
|
|
||||||
this.openpgpPublicKeys.find(key => openPgpKeyToRemove === key)
|
|
||||||
];
|
|
||||||
if (items[0]) {
|
|
||||||
this.openpgpPrivateKeys.remove(items[0]);
|
|
||||||
openpgpKeyring.privateKeys.removeForId(items[0].guid);
|
|
||||||
delegateRunOnDestroy(items[0]);
|
|
||||||
}
|
|
||||||
if (items[1]) {
|
|
||||||
this.openpgpPublicKeys.remove(items[1]);
|
|
||||||
openpgpKeyring.publicKeys.removeForId(items[1].guid);
|
|
||||||
delegateRunOnDestroy(items[1]);
|
|
||||||
}
|
|
||||||
if (items[0] || items[1]) {
|
|
||||||
openpgpKeyring.store();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
decryptMessage(message, recipients, fCallback) {
|
decryptMessage(message, recipients, fCallback) {
|
||||||
if (message && message.getEncryptionKeyIds) {
|
if (message && message.getEncryptionKeyIds) {
|
||||||
|
|
@ -385,7 +379,7 @@ export const PgpUserStore = new class {
|
||||||
// findPublicKeysBySigningKeyIds
|
// findPublicKeysBySigningKeyIds
|
||||||
const publicKeys = signingKeyIds.map(id => {
|
const publicKeys = signingKeyIds.map(id => {
|
||||||
const key = id && id.toHex ? findKeyByHex(this.openpgpPublicKeys, id.toHex()) : null;
|
const key = id && id.toHex ? findKeyByHex(this.openpgpPublicKeys, id.toHex()) : null;
|
||||||
return key ? key.getNativeKeys() : [null];
|
return key ? key.key : [null];
|
||||||
}).flat().filter(v => v);
|
}).flat().filter(v => v);
|
||||||
if (publicKeys && publicKeys.length) {
|
if (publicKeys && publicKeys.length) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -323,12 +323,12 @@
|
||||||
<div class="openpgp-control encrypted" data-bind="visible: pgpEncrypted">
|
<div class="openpgp-control encrypted" data-bind="visible: pgpEncrypted">
|
||||||
<i class="fontastic">🔒</i>
|
<i class="fontastic">🔒</i>
|
||||||
<span data-i18n="MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC"></span>
|
<span data-i18n="MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC"></span>
|
||||||
<button data-bind="visible: pgpSupported, click: pgpDecrypt" data-i18n="OPENPGP/BUTTON_DECRYPT"></button>
|
<button class="btn" data-bind="visible: pgpSupported, click: pgpDecrypt" data-i18n="OPENPGP/BUTTON_DECRYPT"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="openpgp-control signed" data-bind="visible: pgpSigned">
|
<div class="openpgp-control signed" data-bind="visible: pgpSigned">
|
||||||
<i class="fontastic">✍️</i>
|
<i class="fontastic">✍️</i>
|
||||||
<span data-i18n="MESSAGE/PGP_SIGNED_MESSAGE_DESC"></span>
|
<span data-i18n="MESSAGE/PGP_SIGNED_MESSAGE_DESC"></span>
|
||||||
<button data-bind="visible: pgpSupported, click: pgpVerify" data-i18n="MESSAGE/BUTTON_PGP_VERIFY"></button>
|
<button class="btn" data-bind="visible: pgpSupported, click: pgpVerify" data-i18n="MESSAGE/BUTTON_PGP_VERIFY"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bodyText"
|
<div class="bodyText"
|
||||||
|
|
|
||||||
|
|
@ -48,36 +48,36 @@
|
||||||
<tbody><tr><th colspan="4" data-i18n="SETTINGS_OPEN_PGP/TITLE_PRIVATE">Private keys</th></tr></tbody>
|
<tbody><tr><th colspan="4" data-i18n="SETTINGS_OPEN_PGP/TITLE_PRIVATE">Private keys</th></tr></tbody>
|
||||||
<tbody data-bind="foreach: openpgpkeysPrivate, i18nUpdate: openpgpkeysPrivate">
|
<tbody data-bind="foreach: openpgpkeysPrivate, i18nUpdate: openpgpkeysPrivate">
|
||||||
<tr>
|
<tr>
|
||||||
<td data-bind="click: function (openPgpKey) { $root.viewOpenPgpKey(openPgpKey); }">
|
<td data-bind="click: view">
|
||||||
<span class="fontastic" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PRIVATE">🔒</span>
|
<span class="fontastic" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PRIVATE">🔒</span>
|
||||||
<!-- ko foreach: emails -->
|
<!-- ko foreach: emails -->
|
||||||
<span class="open-pgp-key-user" data-bind="text: $data"></span>
|
<span class="open-pgp-key-user" data-bind="text: $data"></span>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-small btn-small-small btn-danger button-confirm-delete" data-bind="css: {'delete-access': deleteAccess()}, click: function(openPgpKey) { $root.deleteOpenPgpKey(openPgpKey); }"
|
<a class="btn btn-small btn-small-small btn-danger button-confirm-delete" data-bind="css: {'delete-access': deleteAccess()}, click: remove"
|
||||||
data-i18n="GLOBAL/ARE_YOU_SURE"></a>
|
data-i18n="GLOBAL/ARE_YOU_SURE"></a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="delete-open-pgp-key fontastic" data-bind="visible: !deleteAccess(), click: function (openPgpKey) { $root.openPgpKeyForDeletion(openPgpKey); }">🗑</span>
|
<span class="delete-open-pgp-key fontastic" data-bind="visible: !deleteAccess(), click: openForDeletion">🗑</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tbody><tr><th colspan="4" data-i18n="SETTINGS_OPEN_PGP/TITLE_PUBLIC">Public keys</th></tr></tbody>
|
<tbody><tr><th colspan="4" data-i18n="SETTINGS_OPEN_PGP/TITLE_PUBLIC">Public keys</th></tr></tbody>
|
||||||
<tbody data-bind="foreach: openpgpkeysPublic, i18nUpdate: openpgpkeysPublic">
|
<tbody data-bind="foreach: openpgpkeysPublic, i18nUpdate: openpgpkeysPublic">
|
||||||
<tr>
|
<tr>
|
||||||
<td data-bind="click: function (openPgpKey) { $root.viewOpenPgpKey(openPgpKey); }">
|
<td data-bind="click: view">
|
||||||
<span class="fontastic" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PUBLIC">🔑</span>
|
<span class="fontastic" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PUBLIC">🔑</span>
|
||||||
<!-- ko foreach: emails -->
|
<!-- ko foreach: emails -->
|
||||||
<span class="open-pgp-key-user" data-bind="text: $data"></span>
|
<span class="open-pgp-key-user" data-bind="text: $data"></span>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-small btn-small-small btn-danger button-confirm-delete" data-bind="css: {'delete-access': deleteAccess()}, click: function(oOpenPGP) { $root.deleteOpenPgpKey(oOpenPGP); }"
|
<a class="btn btn-small btn-small-small btn-danger button-confirm-delete" data-bind="css: {'delete-access': deleteAccess()}, click: remove"
|
||||||
data-i18n="GLOBAL/ARE_YOU_SURE"></a>
|
data-i18n="GLOBAL/ARE_YOU_SURE"></a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="delete-open-pgp-key fontastic" data-bind="visible: !deleteAccess(), click: function (openPgpKey) { $root.openPgpKeyForDeletion(openPgpKey); }">🗑</span>
|
<span class="delete-open-pgp-key fontastic" data-bind="visible: !deleteAccess(), click: openForDeletion">🗑</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue