mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-09-10 15:14:13 +08:00
Many more changes for #89
This commit is contained in:
parent
3cc3a76b23
commit
a7eeeb4f55
18 changed files with 310 additions and 236 deletions
|
@ -4,7 +4,7 @@
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
export const Capa = {
|
export const Capa = {
|
||||||
GnuPGP: 'GNUGP',
|
GnuPG: 'GNUPG',
|
||||||
OpenPGP: 'OPEN_PGP',
|
OpenPGP: 'OPEN_PGP',
|
||||||
Prefetch: 'PREFETCH',
|
Prefetch: 'PREFETCH',
|
||||||
Contacts: 'CONTACTS',
|
Contacts: 'CONTACTS',
|
||||||
|
|
|
@ -21,8 +21,9 @@ import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
import PreviewHTML from 'Html/PreviewMessage.html';
|
import PreviewHTML from 'Html/PreviewMessage.html';
|
||||||
|
|
||||||
const
|
const
|
||||||
/*eslint-disable max-len*/
|
// eslint-disable-next-line max-len
|
||||||
url = /(^|[\s\n]|\/?>)(https:\/\/[-A-Z0-9+\u0026\u2019#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026#/%=~()_|])/gi,
|
url = /(^|[\s\n]|\/?>)(https:\/\/[-A-Z0-9+\u0026\u2019#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026#/%=~()_|])/gi,
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
email = /(^|[\s\n]|\/?>)((?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x21\x23-\x5b\x5d-\x7f]|\\[\x21\x23-\x5b\x5d-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x21-\x5a\x53-\x7f]|\\[\x21\x23-\x5b\x5d-\x7f])+)\]))/gi,
|
email = /(^|[\s\n]|\/?>)((?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x21\x23-\x5b\x5d-\x7f]|\\[\x21\x23-\x5b\x5d-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x21-\x5a\x53-\x7f]|\\[\x21\x23-\x5b\x5d-\x7f])+)\]))/gi,
|
||||||
|
|
||||||
// Removes background and color
|
// Removes background and color
|
||||||
|
@ -644,30 +645,4 @@ export class MessageModel extends AbstractModel {
|
||||||
].join(',');
|
].join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
pgpDecrypt() {
|
|
||||||
// const message = self.message();
|
|
||||||
// message && pgpClickHelper(message.body, message.plain(), message.getEmails(['from', 'to', 'cc']));
|
|
||||||
/*
|
|
||||||
pgpEncrypted: () => PgpUserStore.isSupported()
|
|
||||||
&& MessageUserStore.message() && MessageUserStore.message().isPgpEncrypted(),
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
pgpVerify() {
|
|
||||||
let params = this.pgpSigned(); // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" }
|
|
||||||
if (params) {
|
|
||||||
params.Folder = this.folder;
|
|
||||||
params.Uid = this.uid;
|
|
||||||
rl.app.Remote.post('MessagePgpVerify', null, params)
|
|
||||||
.then(data => {
|
|
||||||
// TODO
|
|
||||||
console.dir(data);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// TODO
|
|
||||||
console.dir(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,20 +5,17 @@ import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
|
|
||||||
export class OpenPgpKeyModel extends AbstractModel {
|
export class OpenPgpKeyModel extends AbstractModel {
|
||||||
/**
|
/**
|
||||||
* @param {string} index
|
|
||||||
* @param {string} guID
|
* @param {string} guID
|
||||||
* @param {string} ID
|
* @param {string} ID
|
||||||
* @param {array} IDs
|
* @param {array} IDs
|
||||||
* @param {array} userIDs
|
* @param {array} userIDs
|
||||||
* @param {array} emails
|
* @param {array} emails
|
||||||
* @param {boolean} isPrivate
|
|
||||||
* @param {string} armor
|
* @param {string} armor
|
||||||
* @param {string} userID
|
* @param {string} userID
|
||||||
*/
|
*/
|
||||||
constructor(index, guID, ID, IDs, userIDs, emails, isPrivate, armor, userID) {
|
constructor(guID, ID, IDs, userIDs, emails, armor, userID) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.index = index;
|
|
||||||
this.id = ID;
|
this.id = ID;
|
||||||
this.ids = arrayLength(IDs) ? IDs : [ID];
|
this.ids = arrayLength(IDs) ? IDs : [ID];
|
||||||
this.guid = guID;
|
this.guid = guID;
|
||||||
|
@ -27,7 +24,6 @@ export class OpenPgpKeyModel extends AbstractModel {
|
||||||
this.email = '';
|
this.email = '';
|
||||||
this.emails = emails;
|
this.emails = emails;
|
||||||
this.armor = armor;
|
this.armor = armor;
|
||||||
this.isPrivate = !!isPrivate;
|
|
||||||
|
|
||||||
if (this.users) {
|
if (this.users) {
|
||||||
const index = this.users.indexOf(userID);
|
const index = this.users.indexOf(userID);
|
||||||
|
|
|
@ -57,7 +57,7 @@ export class SettingsUserScreen extends AbstractSettingsScreen {
|
||||||
settingsAddViewModel(ThemesUserSettings, 'SettingsThemes', 'SETTINGS_LABELS/LABEL_THEMES_NAME', 'themes');
|
settingsAddViewModel(ThemesUserSettings, 'SettingsThemes', 'SETTINGS_LABELS/LABEL_THEMES_NAME', 'themes');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.capa(Capa.OpenPGP) || Settings.capa(Capa.GnuPGP)) {
|
if (Settings.capa(Capa.OpenPGP) || Settings.capa(Capa.GnuPG)) {
|
||||||
settingsAddViewModel(OpenPgpUserSettings, 'SettingsOpenPGP', 'OpenPGP', 'openpgp');
|
settingsAddViewModel(OpenPgpUserSettings, 'SettingsOpenPGP', 'OpenPGP', 'openpgp');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
import { delegateRunOnDestroy } from 'Common/UtilsUser';
|
|
||||||
|
|
||||||
import { PgpUserStore } from 'Stores/User/Pgp';
|
import { PgpUserStore } from 'Stores/User/Pgp';
|
||||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
|
|
||||||
|
@ -15,12 +13,15 @@ import { ViewOpenPgpKeyPopupView } from 'View/Popup/ViewOpenPgpKey';
|
||||||
|
|
||||||
export class OpenPgpUserSettings /*extends AbstractViewSettings*/ {
|
export class OpenPgpUserSettings /*extends AbstractViewSettings*/ {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.openpgpkeys = PgpUserStore.openpgpkeys;
|
this.gnupgkeys = PgpUserStore.gnupgKeys;
|
||||||
this.openpgpkeysPublic = PgpUserStore.openpgpkeysPublic;
|
|
||||||
this.openpgpkeysPrivate = PgpUserStore.openpgpkeysPrivate;
|
|
||||||
|
|
||||||
|
this.openpgpkeysPublic = PgpUserStore.openpgpPublicKeys;
|
||||||
|
this.openpgpkeysPrivate = PgpUserStore.openpgpPrivateKeys;
|
||||||
this.openPgpKeyForDeletion = ko.observable(null).deleteAccessHelper();
|
this.openPgpKeyForDeletion = ko.observable(null).deleteAccessHelper();
|
||||||
|
|
||||||
|
this.canOpenPGP = !!PgpUserStore.openpgpKeyring;
|
||||||
|
// this.canOpenPGP = Settings.capa(Capa.OpenPGP);
|
||||||
|
|
||||||
this.allowDraftAutosave = SettingsUserStore.allowDraftAutosave;
|
this.allowDraftAutosave = SettingsUserStore.allowDraftAutosave;
|
||||||
|
|
||||||
this.allowDraftAutosave.subscribe(value => Remote.saveSetting('AllowDraftAutosave', value ? 1 : 0))
|
this.allowDraftAutosave.subscribe(value => Remote.saveSetting('AllowDraftAutosave', value ? 1 : 0))
|
||||||
|
@ -47,20 +48,7 @@ export class OpenPgpUserSettings /*extends AbstractViewSettings*/ {
|
||||||
deleteOpenPgpKey(openPgpKeyToRemove) {
|
deleteOpenPgpKey(openPgpKeyToRemove) {
|
||||||
if (openPgpKeyToRemove && openPgpKeyToRemove.deleteAccess()) {
|
if (openPgpKeyToRemove && openPgpKeyToRemove.deleteAccess()) {
|
||||||
this.openPgpKeyForDeletion(null);
|
this.openPgpKeyForDeletion(null);
|
||||||
|
PgpUserStore.deleteKey(openPgpKeyToRemove);
|
||||||
if (openPgpKeyToRemove && PgpUserStore.openpgpKeyring) {
|
|
||||||
const findedItem = PgpUserStore.openpgpkeys.find(key => openPgpKeyToRemove === key);
|
|
||||||
if (findedItem) {
|
|
||||||
PgpUserStore.openpgpkeys.remove(findedItem);
|
|
||||||
delegateRunOnDestroy(findedItem);
|
|
||||||
|
|
||||||
PgpUserStore.openpgpKeyring[findedItem.isPrivate ? 'privateKeys' : 'publicKeys'].removeForId(findedItem.guid);
|
|
||||||
|
|
||||||
PgpUserStore.openpgpKeyring.store();
|
|
||||||
}
|
|
||||||
|
|
||||||
PgpUserStore.reloadOpenPgpKeys();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ko from 'ko';
|
||||||
import { Capa } from 'Common/Enums';
|
import { Capa } from 'Common/Enums';
|
||||||
import { doc, createElement, Settings } from 'Common/Globals';
|
import { doc, createElement, Settings } from 'Common/Globals';
|
||||||
import { openPgpJs, openPgpWorkerJs } from 'Common/Links';
|
import { openPgpJs, openPgpWorkerJs } from 'Common/Links';
|
||||||
import { isArray, arrayLength, pString, addComputablesTo } from 'Common/Utils';
|
import { isArray, arrayLength } from 'Common/Utils';
|
||||||
import { delegateRunOnDestroy } from 'Common/UtilsUser';
|
import { delegateRunOnDestroy } from 'Common/UtilsUser';
|
||||||
|
|
||||||
import { showScreenPopup } from 'Knoin/Knoin';
|
import { showScreenPopup } from 'Knoin/Knoin';
|
||||||
|
@ -16,27 +16,25 @@ import { OpenPgpKeyModel } from 'Model/OpenPgpKey';
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
const
|
const
|
||||||
findKeyByHex = (keys, hash, isPrivate) =>
|
findKeyByHex = (keys, hash) =>
|
||||||
keys.find(item => item && isPrivate == item.isPrivate && (hash === item.id || item.ids.includes(hash))),
|
keys.find(item => item && (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 {
|
export const PgpUserStore = new class {
|
||||||
constructor() {
|
constructor() {
|
||||||
// PECL gnupg / PEAR Crypt_GPG
|
/**
|
||||||
this.gnupgkeys;
|
* PECL gnupg / PEAR Crypt_GPG
|
||||||
|
* [ {email, can_encrypt, can_sign}, ... ]
|
||||||
|
*/
|
||||||
|
this.gnupgKeyring;
|
||||||
|
this.gnupgKeys = ko.observableArray();
|
||||||
|
|
||||||
// OpenPGP.js
|
// OpenPGP.js
|
||||||
this.openpgpkeys = ko.observableArray();
|
|
||||||
this.openpgpKeyring = null;
|
this.openpgpKeyring = null;
|
||||||
|
this.openpgpPublicKeys = ko.observableArray();
|
||||||
|
this.openpgpPrivateKeys = ko.observableArray();
|
||||||
|
|
||||||
// https://mailvelope.github.io/mailvelope/Keyring.html
|
// https://mailvelope.github.io/mailvelope/Keyring.html
|
||||||
this.mailvelopeKeyring = null;
|
this.mailvelopeKeyring = null;
|
||||||
|
|
||||||
addComputablesTo(this, {
|
|
||||||
openpgpkeysPublic: () => this.openpgpkeys.filter(item => item && !item.isPrivate),
|
|
||||||
openpgpkeysPrivate: () => this.openpgpkeys.filter(item => item && item.isPrivate)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
@ -88,12 +86,14 @@ export const PgpUserStore = new class {
|
||||||
this.reloadOpenPgpKeys();
|
this.reloadOpenPgpKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.capa(Capa.GnuPGP)) {
|
if (Settings.capa(Capa.GnuPG)) {
|
||||||
this.gnupgkeys = [];
|
this.gnupgKeyring = null;
|
||||||
Remote.request('GnupgGetKeysEmails',
|
this.gnupgKeys([]);
|
||||||
|
Remote.request('GnupgGetKeys',
|
||||||
(iError, oData) => {
|
(iError, oData) => {
|
||||||
if (oData.Result) {
|
if (oData && oData.Result) {
|
||||||
this.gnupgkeys = oData.Result;
|
this.gnupgKeyring = oData.Result;
|
||||||
|
this.gnupgKeys(Object.values(oData.Result));
|
||||||
console.log('gnupg ready');
|
console.log('gnupg ready');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,10 +103,11 @@ export const PgpUserStore = new class {
|
||||||
|
|
||||||
reloadOpenPgpKeys() {
|
reloadOpenPgpKeys() {
|
||||||
if (this.openpgpKeyring) {
|
if (this.openpgpKeyring) {
|
||||||
const keys = [],
|
const publicKeys = [],
|
||||||
|
privateKeys = [],
|
||||||
email = new EmailModel();
|
email = new EmailModel();
|
||||||
|
|
||||||
this.openpgpKeyring.getAllKeys().forEach((oItem, iIndex) => {
|
this.openpgpKeyring.getAllKeys().forEach(oItem => {
|
||||||
if (oItem && oItem.primaryKey) {
|
if (oItem && oItem.primaryKey) {
|
||||||
const aEmails = [],
|
const aEmails = [],
|
||||||
aUsers = [],
|
aUsers = [],
|
||||||
|
@ -132,9 +133,8 @@ export const PgpUserStore = new class {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aEmails.length) {
|
if (aEmails.length) {
|
||||||
keys.push(
|
(oItem.isPrivate() ? privateKeys : publicKeys).push(
|
||||||
new OpenPgpKeyModel(
|
new OpenPgpKeyModel(
|
||||||
iIndex,
|
|
||||||
oItem.primaryKey.getFingerprint(),
|
oItem.primaryKey.getFingerprint(),
|
||||||
oItem.primaryKey
|
oItem.primaryKey
|
||||||
.getKeyId()
|
.getKeyId()
|
||||||
|
@ -154,8 +154,12 @@ export const PgpUserStore = new class {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
delegateRunOnDestroy(this.openpgpkeys());
|
delegateRunOnDestroy(this.openpgpPublicKeys());
|
||||||
this.openpgpkeys(keys);
|
this.openpgpPublicKeys(publicKeys);
|
||||||
|
|
||||||
|
delegateRunOnDestroy(this.openpgpPrivateKeys());
|
||||||
|
this.openpgpPrivateKeys(privateKeys);
|
||||||
|
|
||||||
console.log('openpgp.js ready');
|
console.log('openpgp.js ready');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,48 +171,19 @@ export const PgpUserStore = new class {
|
||||||
return !!(window.openpgp || window.mailvelope);
|
return !!(window.openpgp || window.mailvelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
findPublicKeyByHex(hash) {
|
gnupgImportKey(key, callback) {
|
||||||
return findKeyByHex(this.openpgpkeys, hash, 0);
|
if (Settings.capa(Capa.GnuPG)) {
|
||||||
}
|
Remote.request('GnupgImportKey',
|
||||||
|
(iError, oData) => {
|
||||||
findPrivateKeyByHex(hash) {
|
if (oData && oData.Result) {
|
||||||
return findKeyByHex(this.openpgpkeys, hash, 1);
|
// this.gnupgKeyring = oData.Result;
|
||||||
}
|
}
|
||||||
|
callback && callback(iError, oData);
|
||||||
findPublicKeysByEmail(email) {
|
}, {
|
||||||
return this.openpgpkeysPublic().map(item => {
|
Key: key
|
||||||
const key = item && item.emails.includes(email) ? item : null;
|
}
|
||||||
return key ? key.getNativeKeys() : [null];
|
);
|
||||||
}).flat().filter(v => v);
|
|
||||||
}
|
|
||||||
|
|
||||||
findPublicKeysBySigningKeyIds(signingKeyIds) {
|
|
||||||
return signingKeyIds.map(id => {
|
|
||||||
const key = id && id.toHex ? this.findPublicKeyByHex(id.toHex()) : null;
|
|
||||||
return key ? key.getNativeKeys() : [null];
|
|
||||||
}).flat().filter(v => v);
|
|
||||||
}
|
|
||||||
|
|
||||||
findPrivateKeysByEncryptionKeyIds(encryptionKeyIds, recipients, returnWrapKeys) {
|
|
||||||
let result = isArray(encryptionKeyIds)
|
|
||||||
? encryptionKeyIds.map(id => {
|
|
||||||
const key = id && id.toHex ? this.findPrivateKeyByHex(id.toHex()) : null;
|
|
||||||
return key ? (returnWrapKeys ? [key] : key.getNativeKeys()) : [null];
|
|
||||||
}).flat().filter(v => v)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (!result.length && arrayLength(recipients)) {
|
|
||||||
result = recipients.map(sEmail => {
|
|
||||||
const keys = sEmail ? this.findAllPrivateKeysByEmailNotNative(sEmail) : null;
|
|
||||||
return keys
|
|
||||||
? returnWrapKeys
|
|
||||||
? keys
|
|
||||||
: keys.map(key => key.getNativeKeys()).flat()
|
|
||||||
: [null];
|
|
||||||
}).flat().validUnique(key => key.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -216,45 +191,43 @@ export const PgpUserStore = new class {
|
||||||
* Returns the first library that can.
|
* Returns the first library that can.
|
||||||
*/
|
*/
|
||||||
async hasPublicKeyForEmails(recipients, all) {
|
async hasPublicKeyForEmails(recipients, all) {
|
||||||
const
|
const count = recipients.length;
|
||||||
count = recipients.length,
|
if (count) {
|
||||||
openpgp = count && this.openpgpkeys && recipients.filter(email =>
|
let length = this.gnupgKeyring && recipients.filter(email =>
|
||||||
this.openpgpkeys.find(item => item && !item.isPrivate && item.emails.includes(email))
|
this.gnupgKeyring[email] && this.gnupgKeyring[email].can_encrypt).length;
|
||||||
);
|
|
||||||
|
|
||||||
if (this.gnupgkeys) {
|
|
||||||
let length = recipients.filter(email => this.gnupgkeys[email] && this.gnupgkeys[email].can_encrypt).length;
|
|
||||||
if (length && (!all || length === count)) {
|
if (length && (!all || length === count)) {
|
||||||
return 'gnupg';
|
return 'gnupg';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (openpgp && openpgp.length && (!all || openpgp.length === count)) {
|
length = this.openpgpKeyring && recipients.filter(email =>
|
||||||
return 'openpgp';
|
this.openpgpKeyring.publicKeys.getForAddress(email).length
|
||||||
}
|
).length;
|
||||||
|
if (openpgp && (!all || openpgp === count)) {
|
||||||
|
return 'openpgp';
|
||||||
|
}
|
||||||
|
|
||||||
let mailvelope = count && this.mailvelopeKeyring && await this.mailvelopeKeyring.validKeyForAddress(recipients)
|
let mailvelope = this.mailvelopeKeyring && await this.mailvelopeKeyring.validKeyForAddress(recipients)
|
||||||
/*.then(LookupResult => Object.entries(LookupResult))*/;
|
/*.then(LookupResult => Object.entries(LookupResult))*/;
|
||||||
mailvelope = Object.entries(mailvelope);
|
mailvelope = Object.entries(mailvelope);
|
||||||
if (mailvelope && mailvelope.length
|
if (mailvelope && mailvelope.length
|
||||||
&& (all ? (mailvelope.filter(([, value]) => value).length === count) : mailvelope.find(([, value]) => value))
|
&& (all ? (mailvelope.filter(([, value]) => value).length === count) : mailvelope.find(([, value]) => value))
|
||||||
) {
|
) {
|
||||||
return 'mailvelope';
|
return 'mailvelope';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if signing/decrypting a message is possible with given email address.
|
* Checks if signing a message is possible with given email address.
|
||||||
* Returns the first library that can.
|
* Returns the first library that can.
|
||||||
*/
|
*/
|
||||||
async hasPrivateKeyForEmail(email) {
|
async hasPrivateKeyFor(email, sign) {
|
||||||
if (this.gnupgkeys && this.gnupgkeys[email] && this.gnupgkeys[email].can_sign) {
|
if (this.gnupgKeyring && this.gnupgKeyring[email] && this.gnupgKeyring[email][sign?'can_sign':'can_decrypt']) {
|
||||||
return 'gnupg';
|
return 'gnupg';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.openpgpkeys && this.openpgpkeys.find(item => item && item.isPrivate && item.emails.includes(email))) {
|
if (this.openpgpKeyring && this.openpgpKeyring.privateKeys.getForAddress(email).length) {
|
||||||
return 'openpgp';
|
return 'openpgp';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,47 +246,70 @@ export const PgpUserStore = new class {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} email
|
* Checks if signing a message is possible with given email address.
|
||||||
* @returns {?}
|
* Returns the first library that can.
|
||||||
*/
|
*/
|
||||||
findAllPublicKeysByEmailNotNative(email) {
|
async hasKeyForSigning(email) {
|
||||||
return findAllKeysByEmail(this.openpgpkeys, email, 0);
|
return await this.hasPrivateKeyFor(email, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} email
|
* Checks if decrypting a message is possible with given email address.
|
||||||
* @returns {?}
|
* Returns the first library that can.
|
||||||
*/
|
*/
|
||||||
findAllPrivateKeysByEmailNotNative(email) {
|
async hasKeyForDecrypting(email) {
|
||||||
return findAllKeysByEmail(this.openpgpkeys, email, 1);
|
return await this.hasPrivateKeyFor(email, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} email
|
* OpenPGP.js
|
||||||
* @param {string=} password
|
|
||||||
* @returns {?}
|
|
||||||
*/
|
*/
|
||||||
findPrivateKeyByEmail(email, password) {
|
|
||||||
let privateKey = null;
|
|
||||||
const key = this.openpgpkeys.find(item => item && item.isPrivate && item.emails.includes(email));
|
|
||||||
|
|
||||||
if (key) {
|
/**
|
||||||
try {
|
* @param {OpenPgpKeyModel} openPgpKeyToRemove
|
||||||
privateKey = key.getNativeKeys()[0] || null;
|
* @returns {void}
|
||||||
if (privateKey) {
|
*/
|
||||||
privateKey.decrypt(pString(password));
|
deleteKey(openPgpKeyToRemove) {
|
||||||
|
if (openPgpKeyToRemove && openPgpKeyToRemove.deleteAccess() && this.openpgpKeyring) {
|
||||||
|
let findedItem = this.openpgpPublicKeys.find(key => openPgpKeyToRemove === key);
|
||||||
|
if (findedItem) {
|
||||||
|
this.openpgpPublicKeys.remove(findedItem);
|
||||||
|
this.openpgpKeyring.publicKeys.removeForId(findedItem.guid);
|
||||||
|
} else {
|
||||||
|
findedItem = this.openpgpPrivateKeys.find(key => openPgpKeyToRemove === key);
|
||||||
|
if (findedItem) {
|
||||||
|
this.openpgpPrivateKeys.remove(findedItem);
|
||||||
|
this.openpgpKeyring.privateKeys.removeForId(findedItem.guid);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
privateKey = null;
|
|
||||||
}
|
}
|
||||||
|
if (findedItem) {
|
||||||
|
delegateRunOnDestroy(findedItem);
|
||||||
|
this.openpgpKeyring.store();
|
||||||
|
}
|
||||||
|
// this.reloadOpenPgpKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
return privateKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptMessage(message, recipients, fCallback) {
|
decryptMessage(message, recipients, fCallback) {
|
||||||
if (message && message.getEncryptionKeyIds) {
|
if (message && message.getEncryptionKeyIds) {
|
||||||
const privateKeys = this.findPrivateKeysByEncryptionKeyIds(message.getEncryptionKeyIds(), recipients, true);
|
// findPrivateKeysByEncryptionKeyIds
|
||||||
|
const encryptionKeyIds = message.getEncryptionKeyIds();
|
||||||
|
let privateKeys = isArray(encryptionKeyIds)
|
||||||
|
? encryptionKeyIds.map(id => {
|
||||||
|
// openpgpKeyring.publicKeys.getForId(id.toHex())
|
||||||
|
// openpgpKeyring.privateKeys.getForId(id.toHex())
|
||||||
|
const key = id && id.toHex ? findKeyByHex(this.openpgpPrivateKeys, id.toHex()) : null;
|
||||||
|
return key ? [key] : [null];
|
||||||
|
}).flat().filter(v => v)
|
||||||
|
: [];
|
||||||
|
if (!privateKeys.length && arrayLength(recipients)) {
|
||||||
|
privateKeys = recipients.map(sEmail =>
|
||||||
|
(sEmail
|
||||||
|
? this.openpgpPrivateKeys.filter(item => item && item.emails.includes(sEmail)) : 0)
|
||||||
|
|| [null]
|
||||||
|
).flat().validUnique(key => key.id);
|
||||||
|
}
|
||||||
|
|
||||||
if (privateKeys && privateKeys.length) {
|
if (privateKeys && privateKeys.length) {
|
||||||
showScreenPopup(MessageOpenPgpPopupView, [
|
showScreenPopup(MessageOpenPgpPopupView, [
|
||||||
(decryptedKey) => {
|
(decryptedKey) => {
|
||||||
|
@ -322,7 +318,7 @@ export const PgpUserStore = new class {
|
||||||
(decryptedMessage) => {
|
(decryptedMessage) => {
|
||||||
let privateKey = null;
|
let privateKey = null;
|
||||||
if (decryptedMessage) {
|
if (decryptedMessage) {
|
||||||
privateKey = this.findPrivateKeyByHex(decryptedKey.primaryKey.keyid.toHex());
|
privateKey = findKeyByHex(this.openpgpPrivateKeys, decryptedKey.primaryKey.keyid.toHex());
|
||||||
if (privateKey) {
|
if (privateKey) {
|
||||||
this.verifyMessage(decryptedMessage, (oValidKey, aSigningKeyIds) => {
|
this.verifyMessage(decryptedMessage, (oValidKey, aSigningKeyIds) => {
|
||||||
fCallback(privateKey, decryptedMessage, oValidKey || null, aSigningKeyIds || null);
|
fCallback(privateKey, decryptedMessage, oValidKey || null, aSigningKeyIds || null);
|
||||||
|
@ -358,14 +354,18 @@ export const PgpUserStore = new class {
|
||||||
if (message && message.getSigningKeyIds) {
|
if (message && message.getSigningKeyIds) {
|
||||||
const signingKeyIds = message.getSigningKeyIds();
|
const signingKeyIds = message.getSigningKeyIds();
|
||||||
if (signingKeyIds && signingKeyIds.length) {
|
if (signingKeyIds && signingKeyIds.length) {
|
||||||
const publicKeys = this.findPublicKeysBySigningKeyIds(signingKeyIds);
|
// findPublicKeysBySigningKeyIds
|
||||||
|
const publicKeys = signingKeyIds.map(id => {
|
||||||
|
const key = id && id.toHex ? findKeyByHex(this.openpgpPublicKeys, id.toHex()) : null;
|
||||||
|
return key ? key.getNativeKeys() : [null];
|
||||||
|
}).flat().filter(v => v);
|
||||||
if (publicKeys && publicKeys.length) {
|
if (publicKeys && publicKeys.length) {
|
||||||
try {
|
try {
|
||||||
const result = message.verify(publicKeys),
|
const result = message.verify(publicKeys),
|
||||||
valid = (isArray(result) ? result : []).find(item => item && item.valid && item.keyid);
|
valid = (isArray(result) ? result : []).find(item => item && item.valid && item.keyid);
|
||||||
|
|
||||||
if (valid && valid.keyid && valid.keyid && valid.keyid.toHex) {
|
if (valid && valid.keyid && valid.keyid && valid.keyid.toHex) {
|
||||||
fCallback(this.findPublicKeyByHex(valid.keyid.toHex()));
|
fCallback(findKeyByHex(this.openpgpPublicKeys, valid.keyid.toHex()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -3,6 +3,9 @@ import { PgpUserStore } from 'Stores/User/Pgp';
|
||||||
import { decorateKoCommands } from 'Knoin/Knoin';
|
import { decorateKoCommands } from 'Knoin/Knoin';
|
||||||
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
||||||
|
|
||||||
|
import { Capa } from 'Common/Enums';
|
||||||
|
import { Settings } from 'Common/Globals';
|
||||||
|
|
||||||
class AddOpenPgpKeyPopupView extends AbstractViewPopup {
|
class AddOpenPgpKeyPopupView extends AbstractViewPopup {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('AddOpenPgpKey');
|
super('AddOpenPgpKey');
|
||||||
|
@ -10,9 +13,15 @@ class AddOpenPgpKeyPopupView extends AbstractViewPopup {
|
||||||
this.addObservables({
|
this.addObservables({
|
||||||
key: '',
|
key: '',
|
||||||
keyError: false,
|
keyError: false,
|
||||||
keyErrorMessage: ''
|
keyErrorMessage: '',
|
||||||
|
|
||||||
|
saveGnuPG: true,
|
||||||
|
saveOpenPGP: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.canGnuPG = Settings.capa(Capa.GnuPG);
|
||||||
|
this.canOpenPGP = Settings.capa(Capa.OpenPGP);
|
||||||
|
|
||||||
this.key.subscribe(() => {
|
this.key.subscribe(() => {
|
||||||
this.keyError(false);
|
this.keyError(false);
|
||||||
this.keyErrorMessage('');
|
this.keyErrorMessage('');
|
||||||
|
@ -24,36 +33,40 @@ class AddOpenPgpKeyPopupView extends AbstractViewPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
addOpenPgpKeyCommand() {
|
addOpenPgpKeyCommand() {
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const reg = /[-]{3,6}BEGIN[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[-]{3,6}[\s\S]+?[-]{3,6}END[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[-]{3,6}/gi,
|
|
||||||
openpgpKeyring = PgpUserStore.openpgpKeyring;
|
|
||||||
|
|
||||||
let keyTrimmed = this.key().trim();
|
let keyTrimmed = this.key().trim();
|
||||||
|
|
||||||
if (/[\n]/.test(keyTrimmed)) {
|
if (/\n/.test(keyTrimmed)) {
|
||||||
keyTrimmed = keyTrimmed.replace(/[\r]+/g, '').replace(/[\n]{2,}/g, '\n\n');
|
keyTrimmed = keyTrimmed.replace(/\r+/g, '').replace(/\n{2,}/g, '\n\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.keyError(!keyTrimmed);
|
this.keyError(!keyTrimmed);
|
||||||
this.keyErrorMessage('');
|
this.keyErrorMessage('');
|
||||||
|
|
||||||
if (!openpgpKeyring || this.keyError()) {
|
if (!keyTrimmed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let match = null,
|
let match = null,
|
||||||
count = 30,
|
count = 30,
|
||||||
done = false;
|
done = false;
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const reg = /[-]{3,6}BEGIN[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[-]{3,6}[\s\S]+?[-]{3,6}END[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[-]{3,6}/gi,
|
||||||
|
keyring = PgpUserStore.openpgpKeyring;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
match = reg.exec(keyTrimmed);
|
match = reg.exec(keyTrimmed);
|
||||||
if (match && 0 < count) {
|
if (match && 0 < count) {
|
||||||
if (match[0] && match[1] && match[2] && match[1] === match[2]) {
|
if (match[0] && match[1] && match[2] && match[1] === match[2]) {
|
||||||
let err = null;
|
let err = null;
|
||||||
if ('PRIVATE' === match[1]) {
|
if (this.saveGnuPG()) {
|
||||||
err = openpgpKeyring.privateKeys.importKey(match[0]);
|
PgpUserStore.gnupgImportKey(this.key());
|
||||||
} else if ('PUBLIC' === match[1]) {
|
}
|
||||||
err = openpgpKeyring.publicKeys.importKey(match[0]);
|
if (this.canOpenPGP && this.saveOpenPGP()) {
|
||||||
|
if ('PRIVATE' === match[1]) {
|
||||||
|
err = keyring.privateKeys.importKey(match[0]);
|
||||||
|
} else if ('PUBLIC' === match[1]) {
|
||||||
|
err = keyring.publicKeys.importKey(match[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -70,9 +83,11 @@ class AddOpenPgpKeyPopupView extends AbstractViewPopup {
|
||||||
}
|
}
|
||||||
} while (!done);
|
} while (!done);
|
||||||
|
|
||||||
openpgpKeyring.store();
|
if (this.canOpenPGP && this.saveOpenPGP()) {
|
||||||
|
keyring.store();
|
||||||
|
}
|
||||||
|
|
||||||
PgpUserStore.reloadOpenPgpKeys();
|
// PgpUserStore.reloadOpenPgpKeys();
|
||||||
|
|
||||||
if (this.keyError()) {
|
if (this.keyError()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -133,8 +133,6 @@ class ComposePopupView extends AbstractViewPopup {
|
||||||
|
|
||||||
this.capaOpenPGP = PgpUserStore.isSupported();
|
this.capaOpenPGP = PgpUserStore.isSupported();
|
||||||
|
|
||||||
this.identities = IdentityUserStore;
|
|
||||||
|
|
||||||
this.addObservables({
|
this.addObservables({
|
||||||
identitiesDropdownTrigger: false,
|
identitiesDropdownTrigger: false,
|
||||||
|
|
||||||
|
@ -189,7 +187,7 @@ class ComposePopupView extends AbstractViewPopup {
|
||||||
// div.textAreaParent
|
// div.textAreaParent
|
||||||
composeEditorArea: null,
|
composeEditorArea: null,
|
||||||
|
|
||||||
currentIdentity: this.identities()[0] ? this.identities()[0] : null
|
currentIdentity: IdentityUserStore()[0] || null
|
||||||
});
|
});
|
||||||
|
|
||||||
// this.to.subscribe((v) => console.log(v));
|
// this.to.subscribe((v) => console.log(v));
|
||||||
|
@ -281,7 +279,7 @@ class ComposePopupView extends AbstractViewPopup {
|
||||||
|
|
||||||
currentIdentity: value => {
|
currentIdentity: value => {
|
||||||
this.canPgpSign(false);
|
this.canPgpSign(false);
|
||||||
value && PgpUserStore.hasPrivateKeyForEmail(value.email()).then(result => {
|
value && PgpUserStore.hasKeyForSigning(value.email()).then(result => {
|
||||||
console.log({canPgpSign:result});
|
console.log({canPgpSign:result});
|
||||||
this.canPgpSign(result)
|
this.canPgpSign(result)
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { pInt } from 'Common/Utils';
|
import { pInt } from 'Common/Utils';
|
||||||
|
|
||||||
import { PgpUserStore } from 'Stores/User/Pgp';
|
import { PgpUserStore } from 'Stores/User/Pgp';
|
||||||
|
import { IdentityUserStore } from 'Stores/User/Identity';
|
||||||
|
|
||||||
import { decorateKoCommands } from 'Knoin/Knoin';
|
import { decorateKoCommands } from 'Knoin/Knoin';
|
||||||
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
||||||
|
@ -9,6 +10,8 @@ class NewOpenPgpKeyPopupView extends AbstractViewPopup {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('NewOpenPgpKey');
|
super('NewOpenPgpKey');
|
||||||
|
|
||||||
|
this.identities = IdentityUserStore;
|
||||||
|
|
||||||
this.addObservables({
|
this.addObservables({
|
||||||
email: '',
|
email: '',
|
||||||
emailError: false,
|
emailError: false,
|
||||||
|
@ -63,6 +66,9 @@ class NewOpenPgpKeyPopupView extends AbstractViewPopup {
|
||||||
openpgpKeyring.store();
|
openpgpKeyring.store();
|
||||||
|
|
||||||
PgpUserStore.reloadOpenPgpKeys();
|
PgpUserStore.reloadOpenPgpKeys();
|
||||||
|
|
||||||
|
PgpUserStore.gnupgImportKey(keyPair.privateKeyArmored);
|
||||||
|
|
||||||
this.cancelCommand();
|
this.cancelCommand();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -87,13 +93,11 @@ class NewOpenPgpKeyPopupView extends AbstractViewPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
this.name('');
|
this.name(IdentityUserStore()[0].name());
|
||||||
this.password('');
|
this.password('');
|
||||||
|
this.email(IdentityUserStore()[0].email());
|
||||||
this.email('');
|
|
||||||
this.emailError(false);
|
this.emailError(false);
|
||||||
this.keyBitLength(4096);
|
this.keyBitLength(4096);
|
||||||
|
|
||||||
this.submitError('');
|
this.submitError('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,8 +180,7 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
|
|
||||||
pgpSigned: () => MessageUserStore.message() && !!MessageUserStore.message().pgpSigned(),
|
pgpSigned: () => MessageUserStore.message() && !!MessageUserStore.message().pgpSigned(),
|
||||||
|
|
||||||
pgpEncrypted: () => PgpUserStore.isSupported()
|
pgpEncrypted: () => MessageUserStore.message() && MessageUserStore.message().isPgpEncrypted(),
|
||||||
&& MessageUserStore.message() && MessageUserStore.message().isPgpEncrypted(),
|
|
||||||
|
|
||||||
messageListOrViewLoading:
|
messageListOrViewLoading:
|
||||||
() => MessageUserStore.listIsLoading() | MessageUserStore.messageLoading()
|
() => MessageUserStore.listIsLoading() | MessageUserStore.messageLoading()
|
||||||
|
@ -624,11 +623,65 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
|
|
||||||
pgpDecrypt(self) {
|
pgpDecrypt(self) {
|
||||||
const message = self.message();
|
const message = self.message();
|
||||||
message && message.pgpDecrypt();
|
if (message && PgpUserStore.isSupported()) {
|
||||||
|
// pgpClickHelper(message.body, message.plain(), message.getEmails(['from', 'to', 'cc']));
|
||||||
|
/*
|
||||||
|
pgpEncrypted: () => PgpUserStore.isSupported()
|
||||||
|
&& MessageUserStore.message() && MessageUserStore.message().isPgpEncrypted(),
|
||||||
|
*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pgpVerify(self) {
|
pgpVerify(self) {
|
||||||
const message = self.message();
|
const message = self.message();
|
||||||
message && message.pgpVerify();
|
if (message && PgpUserStore.isSupported()) {
|
||||||
|
const mode = PgpUserStore.hasPublicKeyForEmails([message.from[0].email()]);
|
||||||
|
if ('gnupg' === mode) {
|
||||||
|
let params = message.pgpSigned(); // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" }
|
||||||
|
if (params) {
|
||||||
|
params.Folder = message.folder;
|
||||||
|
params.Uid = message.uid;
|
||||||
|
rl.app.Remote.post('MessagePgpVerify', null, params)
|
||||||
|
.then(data => {
|
||||||
|
// TODO
|
||||||
|
console.dir(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// TODO
|
||||||
|
console.dir(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if ('openpgp' === mode) {
|
||||||
|
let text = null;
|
||||||
|
try {
|
||||||
|
text = PgpUserStore.openpgp.cleartext.readArmored(message.plain);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
if (text && text.getText && text.verify) {
|
||||||
|
PgpUserStore.verifyMessage(text, (validKey, signingKeyIds) => {
|
||||||
|
console.dir([validKey, signingKeyIds]);
|
||||||
|
/*
|
||||||
|
if (validKey) {
|
||||||
|
i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
|
||||||
|
USER: validKey.user + ' (' + validKey.id + ')'
|
||||||
|
});
|
||||||
|
message.getText()
|
||||||
|
} else {
|
||||||
|
const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
|
||||||
|
additional = keyIds
|
||||||
|
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
|
||||||
|
: '';
|
||||||
|
|
||||||
|
i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : '');
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1192,7 +1192,7 @@ class Actions
|
||||||
$aResult[] = Enumerations\Capa::OPEN_PGP;
|
$aResult[] = Enumerations\Capa::OPEN_PGP;
|
||||||
}
|
}
|
||||||
if (\SnappyMail\PGP\GnuPG::isSupported()) {
|
if (\SnappyMail\PGP\GnuPG::isSupported()) {
|
||||||
$aResult[] = Enumerations\Capa::GNUGP;
|
$aResult[] = Enumerations\Capa::GNUPG;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($bAdmin || ($oAccount && $oAccount->Domain()->UseSieve())) {
|
if ($bAdmin || ($oAccount && $oAccount->Domain()->UseSieve())) {
|
||||||
|
|
|
@ -16,50 +16,48 @@ trait Pgp
|
||||||
return \SnappyMail\PGP\GnuPG::getInstance($pgp_dir);
|
return \SnappyMail\PGP\GnuPG::getInstance($pgp_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function DoGnupgGetKeysEmails() : array
|
public function DoGnupgGetKeys() : array
|
||||||
{
|
{
|
||||||
$GPG = $this->GnuPG();
|
$GPG = $this->GnuPG();
|
||||||
if ($GPG) {
|
if ($GPG) {
|
||||||
$keys = [];
|
$keys = [];
|
||||||
|
/**
|
||||||
|
* PECL GnuPG can't list private
|
||||||
|
*
|
||||||
|
* gpg --list-secret-keys
|
||||||
|
* gpg --list-public-keys
|
||||||
|
*/
|
||||||
foreach ($GPG->keyInfo('') as $info) {
|
foreach ($GPG->keyInfo('') as $info) {
|
||||||
if (!$info['disabled'] && !$info['expired'] && !$info['revoked']) {
|
if (!$info['disabled'] && !$info['expired'] && !$info['revoked']) {
|
||||||
|
$info['can_verify'] = $info['can_sign'];
|
||||||
|
$info['can_sign'] = $info['can_decrypt'] = false;
|
||||||
|
foreach ($info['subkeys'] as $key) {
|
||||||
|
$hasKey = $GPG->hasPrivateKey($key['keygrip']);
|
||||||
|
$info['can_sign'] = $info['can_sign'] || ($info['can_verify'] && $hasKey);
|
||||||
|
$info['can_decrypt'] = $info['can_decrypt'] || ($info['can_encrypt'] && $hasKey);
|
||||||
|
}
|
||||||
foreach ($info['uids'] as $uid) {
|
foreach ($info['uids'] as $uid) {
|
||||||
$id = $uid['email'];
|
$id = $uid['email'];
|
||||||
if (isset($keys[$id])) {
|
if (isset($keys[$id])) {
|
||||||
$keys[$id]['can_sign'] = $keys[$id]['can_sign'] || $info['can_sign'];
|
// Public Key tasks
|
||||||
|
$keys[$id]['can_verify'] = $keys[$id]['can_verify'] || $info['can_verify'];
|
||||||
$keys[$id]['can_encrypt'] = $keys[$id]['can_encrypt'] || $info['can_encrypt'];
|
$keys[$id]['can_encrypt'] = $keys[$id]['can_encrypt'] || $info['can_encrypt'];
|
||||||
|
// Private Key tasks
|
||||||
|
$keys[$id]['can_sign'] = $keys[$id]['can_sign'] || $info['can_sign'];
|
||||||
|
$keys[$id]['can_decrypt'] = $keys[$id]['can_decrypt'] || $info['can_decrypt'];
|
||||||
} else {
|
} else {
|
||||||
$keys[$id] = [
|
$keys[$id] = [
|
||||||
'name' => $uid['name'],
|
'name' => $uid['name'],
|
||||||
'email' => $uid['email'],
|
'email' => $uid['email'],
|
||||||
|
// Public Key tasks
|
||||||
|
'can_verify' => $info['can_sign'],
|
||||||
|
'can_encrypt' => $info['can_encrypt'],
|
||||||
|
// Private Key tasks
|
||||||
'can_sign' => $info['can_sign'],
|
'can_sign' => $info['can_sign'],
|
||||||
'can_encrypt' => $info['can_encrypt']
|
'can_decrypt' => $info['can_decrypt']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
foreach ($info['subkeys'] as $key) {
|
|
||||||
$key['can_authenticate'] = true
|
|
||||||
$key['can_certify'] = true
|
|
||||||
$key['can_encrypt'] = true
|
|
||||||
$key['can_sign'] = true
|
|
||||||
$key['disabled'] = false
|
|
||||||
$key['expired'] = false
|
|
||||||
$key['expires'] = 0
|
|
||||||
$key['fingerprint'] = "99BBB6F2FDDE9E20CD78B98DC85B364A5A6CCF52"
|
|
||||||
$key['invalid'] = false
|
|
||||||
$key['is_cardkey'] = false
|
|
||||||
$key['is_de_vs'] = true
|
|
||||||
$key['is_qualified'] = false
|
|
||||||
$key['is_secret'] = false
|
|
||||||
$key['keygrip'] = "CBCCF45D4F6D300417F044A08E08F8F14522BABE"
|
|
||||||
$key['keyid'] = "C85B364A5A6CCF52"
|
|
||||||
$key['length'] = 4096
|
|
||||||
$key['pubkey_algo'] = 1
|
|
||||||
$key['revoked'] = false
|
|
||||||
$key['timestamp'] = 1428449321
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $this->DefaultResponse(__FUNCTION__, $keys);
|
return $this->DefaultResponse(__FUNCTION__, $keys);
|
||||||
|
@ -67,13 +65,13 @@ trait Pgp
|
||||||
return $this->FalseResponse(__FUNCTION__);
|
return $this->FalseResponse(__FUNCTION__);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function DoPgpImportKey() : array
|
public function DoGnupgImportKey() : array
|
||||||
{
|
{
|
||||||
|
$sKey = $this->GetActionParam('Key', '');
|
||||||
$sKeyId = $this->GetActionParam('KeyId', '');
|
$sKeyId = $this->GetActionParam('KeyId', '');
|
||||||
$sPublicKey = $this->GetActionParam('PublicKey', '');
|
|
||||||
$sEmail = $this->GetActionParam('Email', '');
|
$sEmail = $this->GetActionParam('Email', '');
|
||||||
|
|
||||||
if (!$sPublicKey) {
|
if (!$sKey) {
|
||||||
try {
|
try {
|
||||||
if (!$sKeyId) {
|
if (!$sKeyId) {
|
||||||
if (\preg_match('/[^\\s<>]+@[^\\s<>]+/', $sEmail, $aMatch)) {
|
if (\preg_match('/[^\\s<>]+@[^\\s<>]+/', $sEmail, $aMatch)) {
|
||||||
|
@ -97,16 +95,16 @@ trait Pgp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($sKeyId) {
|
if ($sKeyId) {
|
||||||
$sPublicKey = \SnappyMail\PGP\Keyservers::get($sKeyId);
|
$sKey = \SnappyMail\PGP\Keyservers::get($sKeyId);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$GPG = $sPublicKey ? $this->GnuPG() : null;
|
$GPG = $sKey ? $this->GnuPG() : null;
|
||||||
return $GPG
|
return $GPG
|
||||||
? $this->DefaultResponse(__FUNCTION__, $GPG->import($sPublicKey))
|
? $this->DefaultResponse(__FUNCTION__, $GPG->import($sKey))
|
||||||
: $this->FalseResponse(__FUNCTION__);
|
: $this->FalseResponse(__FUNCTION__);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,7 +254,7 @@ trait Response
|
||||||
|
|
||||||
$mResult['Plain'] = $mResponse->Plain();
|
$mResult['Plain'] = $mResponse->Plain();
|
||||||
|
|
||||||
// $this->GetCapa(false, Capa::OPEN_PGP) || $this->GetCapa(false, Capa::GNUGP)
|
// $this->GetCapa(false, Capa::OPEN_PGP) || $this->GetCapa(false, Capa::GNUPG)
|
||||||
$mResult['isPgpEncrypted'] = $mResponse->isPgpEncrypted();
|
$mResult['isPgpEncrypted'] = $mResponse->isPgpEncrypted();
|
||||||
$mResult['PgpSigned'] = $mResponse->PgpSigned();
|
$mResult['PgpSigned'] = $mResponse->PgpSigned();
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace RainLoop\Enumerations;
|
||||||
|
|
||||||
class Capa
|
class Capa
|
||||||
{
|
{
|
||||||
const GNUGP = 'GNUGP';
|
const GNUPG = 'GNUPG';
|
||||||
const OPEN_PGP = 'OPEN_PGP';
|
const OPEN_PGP = 'OPEN_PGP';
|
||||||
const PREFETCH = 'PREFETCH';
|
const PREFETCH = 'PREFETCH';
|
||||||
const THEMES = 'THEMES';
|
const THEMES = 'THEMES';
|
||||||
|
|
|
@ -374,6 +374,17 @@ class GnuPG
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with information about all keys that matches the given pattern
|
||||||
|
*/
|
||||||
|
public function hasPrivateKey(string $keygrip) : bool
|
||||||
|
{
|
||||||
|
if ($this->GnuPG || $this->Crypt_GPG) {
|
||||||
|
return \is_file("{$this->homedir}/private-keys-v1.d/{$keygrip}.key");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle armored output
|
* Toggle armored output
|
||||||
* When true the output is ASCII
|
* When true the output is ASCII
|
||||||
|
|
|
@ -8,6 +8,22 @@
|
||||||
<div class="control-group" data-bind="css: {'error': keyError}">
|
<div class="control-group" data-bind="css: {'error': keyError}">
|
||||||
<textarea class="inputKey input-xxlarge" rows="14" autofocus="" autocomplete="off" data-bind="value: key"></textarea>
|
<textarea class="inputKey input-xxlarge" rows="14" autofocus="" autocomplete="off" data-bind="value: key"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<div data-bind="visible: canGnuPG, component: {
|
||||||
|
name: 'Checkbox',
|
||||||
|
params: {
|
||||||
|
label: 'Store in GnuPG',
|
||||||
|
value: saveGnuPG
|
||||||
|
}
|
||||||
|
}"></div>
|
||||||
|
<div data-bind="visible: canOpenPGP, component: {
|
||||||
|
name: 'Checkbox',
|
||||||
|
params: {
|
||||||
|
label: 'Store in OpenPGP.js',
|
||||||
|
value: saveOpenPGP
|
||||||
|
}
|
||||||
|
}"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
|
|
|
@ -9,9 +9,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group" data-bind="css: {'error': emailError}">
|
<div class="control-group" data-bind="css: {'error': emailError}">
|
||||||
<label data-i18n="GLOBAL/EMAIL"></label>
|
<label data-i18n="GLOBAL/EMAIL"></label>
|
||||||
<input type="email" class="input-xlarge"
|
<select class="input-xlarge" data-bind="value: email, options: identities, optionsText: 'email', optionsValue: 'email'"></select>
|
||||||
autofocus="" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
|
|
||||||
data-bind="value: email" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label data-i18n="GLOBAL/NAME"></label>
|
<label data-i18n="GLOBAL/NAME"></label>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<i class="icon-list-add"></i>
|
<i class="icon-list-add"></i>
|
||||||
<span data-i18n="SETTINGS_OPEN_PGP/BUTTON_ADD_OPEN_PGP_KEY"></span>
|
<span data-i18n="SETTINGS_OPEN_PGP/BUTTON_ADD_OPEN_PGP_KEY"></span>
|
||||||
</button>
|
</button>
|
||||||
|
<!-- ko if: canOpenPGP -->
|
||||||
|
|
||||||
<div style="display: inline-block" data-i18n="[title]SETTINGS_OPEN_PGP/GENERATE_ONLY_HTTPS">
|
<div style="display: inline-block" data-i18n="[title]SETTINGS_OPEN_PGP/GENERATE_ONLY_HTTPS">
|
||||||
<button class="btn" data-bind="click: generateOpenPgpKey">
|
<button class="btn" data-bind="click: generateOpenPgpKey">
|
||||||
|
@ -10,6 +11,8 @@
|
||||||
<span data-i18n="SETTINGS_OPEN_PGP/BUTTON_GENERATE_OPEN_PGP_KEYS"></span>
|
<span data-i18n="SETTINGS_OPEN_PGP/BUTTON_GENERATE_OPEN_PGP_KEYS"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- /ko -->
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
|
@ -22,8 +25,27 @@
|
||||||
}"></div>
|
}"></div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<table class="table table-hover list-table" data-bind="i18nUpdate: openpgpkeys">
|
|
||||||
<tbody data-bind="foreach: openpgpkeysPrivate">
|
<h1>GnuPG</h1>
|
||||||
|
<table class="table table-hover list-table">
|
||||||
|
<tbody data-bind="foreach: gnupgkeys, i18nUpdate: gnupgkeys">
|
||||||
|
<tr class="open-pgp-key-item">
|
||||||
|
<td>
|
||||||
|
<span data-bind="visible: can_sign" class="fontastic" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PRIVATE">✍/🔓</span>
|
||||||
|
<td>
|
||||||
|
<td>
|
||||||
|
<span data-bind="visible: can_encrypt" class="fontastic" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PUBLIC">🔒</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- ko text: email --><!-- /ko -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h1>OpenPGP.js</h1>
|
||||||
|
<table class="table table-hover list-table">
|
||||||
|
<tbody data-bind="foreach: openpgpkeysPrivate, i18nUpdate: openpgpkeysPrivate">
|
||||||
<tr class="open-pgp-key-item">
|
<tr class="open-pgp-key-item">
|
||||||
<td>
|
<td>
|
||||||
<span class="open-pgp-key-img fontastic" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PRIVATE">🔒</span>
|
<span class="open-pgp-key-img fontastic" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PRIVATE">🔒</span>
|
||||||
|
@ -40,7 +62,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="view-open-pgp-key fontastic" data-bind="click: function (openPgpKey) { $root.viewOpenPgpKey(openPgpKey); }">👁</td>
|
<td class="view-open-pgp-key fontastic" data-bind="click: function (openPgpKey) { $root.viewOpenPgpKey(openPgpKey); }">👁</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody><tbody data-bind="foreach: openpgpkeysPublic">
|
</tbody><tbody data-bind="foreach: openpgpkeysPublic, i18nUpdate: openpgpkeysPublic">
|
||||||
<tr class="open-pgp-key-item">
|
<tr class="open-pgp-key-item">
|
||||||
<td>
|
<td>
|
||||||
<span class="open-pgp-key-img fontastic" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PUBLIC">🔑</span>
|
<span class="open-pgp-key-img fontastic" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PUBLIC">🔑</span>
|
||||||
|
|
Loading…
Add table
Reference in a new issue