From c012fe09f08fe4135d99125d729ea2469655bc71 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 31 Jan 2022 11:51:15 +0100 Subject: [PATCH] #89 decrypt using OpenPGP.js --- dev/Stores/User/OpenPGP.js | 136 ++++++--------------------- dev/Stores/User/Pgp.js | 16 +++- dev/View/Popup/OpenPgpKey.js | 84 +++++++++++++++++ dev/View/User/MailBox/MessageView.js | 45 +++++---- 4 files changed, 150 insertions(+), 131 deletions(-) diff --git a/dev/Stores/User/OpenPGP.js b/dev/Stores/User/OpenPGP.js index 59ea3ae0e..6137d8e59 100644 --- a/dev/Stores/User/OpenPGP.js +++ b/dev/Stores/User/OpenPGP.js @@ -4,16 +4,13 @@ import ko from 'ko'; -import { isArray, arrayLength } from 'Common/Utils'; +import { arrayLength } from 'Common/Utils'; import { delegateRunOnDestroy } from 'Common/UtilsUser'; import { showScreenPopup } from 'Knoin/Knoin'; import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey'; const - findKeyByHex = (keys, hash) => - keys.find(item => item && (hash === item.id || item.ids.includes(hash))), - findOpenPGPKey = (keys, query/*, sign*/) => keys.find(key => key.emails.includes(query) || query == key.id || query == key.fingerprint @@ -162,114 +159,37 @@ export const OpenPGPUserStore = new class { return findOpenPGPKey(this.publicKeys, query/*, sign*/); } - decrypt(text, fCallback) + /** + * https://docs.openpgpjs.org/#encrypt-and-decrypt-string-data-with-pgp-keys + */ + async decrypt(armoredText, privateKey, publicKey) { -/* - decryptMessage(message, recipients, fCallback) { - message = store.openpgp.message.readArmored(armoredMessage); - try { - message = store.openpgp.message.readArmored(armoredMessage); - } catch (e) { - log(e); + const passphrase = prompt('Passphrase'); + if (!passphrase) { + return; } - if (message && message.getText && message.verify && message.decrypt) { - if (message && message.getEncryptionKeyIds) { - // 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.privateKeys, id.toHex()) : null; - return key ? [key] : [null]; - }).flat().filter(v => v) - : []; - if (!privateKeys.length && arrayLength(recipients)) { - privateKeys = recipients.map(sEmail => - (sEmail - ? this.privateKeys.filter(item => item && item.emails.includes(sEmail)) : 0) - || [null] - ).flat().validUnique(key => key.id); - } - - if (privateKeys && privateKeys.length) { - showScreenPopup(OpenPgpSelectorPopupView, [ - (decryptedKey) => { - if (decryptedKey) { - message.decrypt(decryptedKey).then( - (decryptedMessage) => { - let privateKey = null; - if (decryptedMessage) { - privateKey = findKeyByHex(this.privateKeys, 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); - -*/ + const message = await openpgp.readMessage({ armoredMessage: armoredText }); + const decryptedKey = await openpgp.decryptKey({ +// privateKey: await openpgp.readPrivateKey({ armoredKey: armoredPrivateKey }), + privateKey: privateKey, + passphrase + }); + return await openpgp.decrypt({ + message, + verificationKeys: publicKey, +// expectSigned: true, +// signature: '', // Detached signature + decryptionKeys: decryptedKey + }); } - verify(message, fCallback) { - let text = null; - try { - // TODO: if message.pgpSigned().SigPartId then fetch raw from server - text = openpgp.cleartext.readArmored(message.plain); - } catch (e) { - console.error(e); - } - if (text && text.getText && text.verify) { - if (message && message.getSigningKeyIds) { - const signingKeyIds = message.getSigningKeyIds(); - if (signingKeyIds && signingKeyIds.length) { - // findPublicKeysBySigningKeyIds - const publicKeys = signingKeyIds.map(id => { - const key = id && id.toHex ? findKeyByHex(this.publicKeys, id.toHex()) : null; - return key ? key.key : [null]; - }).flat().filter(v => v); - if (publicKeys && publicKeys.length) { - try { - const result = message.verify(publicKeys), - valid = (isArray(result) ? result : []).find(item => item && item.valid && item.keyid); - - if (valid && valid.keyid && valid.keyid && valid.keyid.toHex) { - fCallback(findKeyByHex(this.publicKeys, valid.keyid.toHex())); - return true; - } - } catch (e) { - console.log(e); - } - } - - fCallback(null, signingKeyIds); - return false; - } - } - - fCallback(null); - return false; + async verify(message, detachedSignature, publicKey) { + return await openpgp.verify({ + message, + verificationKeys: publicKey, +// expectSigned: true, // !!detachedSignature + signature: detachedSignature + }); } }; diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js index 587eca9f4..5538e4826 100644 --- a/dev/Stores/User/Pgp.js +++ b/dev/Stores/User/Pgp.js @@ -131,10 +131,15 @@ export const PgpUserStore = new class { */ async getKeyForDecryption(ids, email) { ids = [email].concat(ids); - let i = ids.length, - key = await this.getMailvelopePrivateKeyFor({email:email}); - if (key) { - return key; + let i = ids.length, key; + + try { + key = await this.getMailvelopePrivateKeyFor(email); + if (key) { + return key; + } + } catch (err) { + console.error(err); } /* Not working, needs full fingerprint while (i--) { @@ -148,13 +153,14 @@ export const PgpUserStore = new class { } i = ids.length; */ +/* while (i--) { key = this.getGnuPGPrivateKeyFor(ids[i]); if (key) { return key; } } - +*/ i = ids.length; while (i--) { key = this.getOpenPGPPrivateKeyFor(ids[i]); diff --git a/dev/View/Popup/OpenPgpKey.js b/dev/View/Popup/OpenPgpKey.js index 2ce1789d5..4469f3630 100644 --- a/dev/View/Popup/OpenPgpKey.js +++ b/dev/View/Popup/OpenPgpKey.js @@ -32,6 +32,90 @@ class OpenPgpKeyPopupView extends AbstractViewPopup { onShow(openPgpKey) { // TODO: show more info this.key(openPgpKey ? openPgpKey.armor : ''); +/* + this.key = key; + const aEmails = []; + if (key.users) { + key.users.forEach(user => 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.askDelete = ko.observable(false); + this.openForDeletion = ko.observable(null).askDeleteHelper(); + + key.id = key.subkeys[0].keyid; + key.fingerprint = key.subkeys[0].fingerprint; + key.uids.forEach(uid => uid.email && aEmails.push(uid.email)); + key.emails = aEmails; + "disabled": false, + "expired": false, + "revoked": false, + "is_secret": true, + "can_sign": true, + "can_decrypt": true + "can_verify": true + "can_encrypt": true, + "uids": [ + { + "name": "demo", + "comment": "", + "email": "demo@snappymail.eu", + "uid": "demo ", + "revoked": false, + "invalid": false + } + ], + "subkeys": [ + { + "fingerprint": "2C223F20EA2ADB4CB68F81D95F3A5CDC09AD8AE3", + "keyid": "5F3A5CDC09AD8AE3", + "timestamp": 1643381672, + "expires": 0, + "is_secret": false, + "invalid": false, + "can_encrypt": false, + "can_sign": true, + "disabled": false, + "expired": false, + "revoked": false, + "can_certify": true, + "can_authenticate": false, + "is_qualified": false, + "is_de_vs": false, + "pubkey_algo": 303, + "length": 256, + "keygrip": "5A1A6C7310D0508C68E8E74F15068301E83FD1AE", + "is_cardkey": false, + "curve": "ed25519" + }, + { + "fingerprint": "3CD720549D8833872C267D08F1230DCE2A561ADE", + "keyid": "F1230DCE2A561ADE", + "timestamp": 1643381672, + "expires": 0, + "is_secret": false, + "invalid": false, + "can_encrypt": true, + "can_sign": false, + "disabled": false, + "expired": false, + "revoked": false, + "can_certify": false, + "can_authenticate": false, + "is_qualified": false, + "is_de_vs": false, + "pubkey_algo": 302, + "length": 256, + "keygrip": "886921A7E06BE56F8E8C51797BB476BB26DF21BF", + "is_cardkey": false, + "curve": "cv25519" + } + ] +*/ } onBuild() { diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index f64bd9958..bd467acf9 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -41,6 +41,7 @@ import { decorateKoCommands, createCommand } from 'Knoin/Knoin'; import { AbstractViewRight } from 'Knoin/AbstractViews'; import { PgpUserStore } from 'Stores/User/Pgp'; +import { OpenPGPUserStore } from 'Stores/User/OpenPGP'; const currentMessage = () => MessageUserStore.message(); @@ -626,6 +627,7 @@ export class MailMessageView extends AbstractViewRight { pgpDecrypt(self) { const message = self.message(), + sender = message && message.from[0].email, pgpInfo = message && message.pgpEncrypted(); if (pgpInfo) { // Also check message.from[0].email @@ -652,7 +654,7 @@ export class MailMessageView extends AbstractViewRight { message.plain(), PgpUserStore.mailvelopeKeyring, { - senderAddress: message.from[0].email + senderAddress: sender } ).then(status => { if (status.error && status.error.message) { @@ -665,8 +667,18 @@ export class MailMessageView extends AbstractViewRight { }); } else if ('openpgp' === result[0]) { - // TODO -// PgpUserStore.decryptMessage(message, recipients, fCallback) + const publicKey = OpenPGPUserStore.getPublicKeyFor(sender); +// OpenPGPUserStore.decrypt(message.plain(), result[1].armor + OpenPGPUserStore.decrypt(message.plain(), result[1].key, publicKey ? publicKey.key : null).then(result => { + if (result) { + // TODO: if result.data is not cleartext then + // parse mime to find and verify signature + message.plain(result.data); + message.viewPlain(); + } else { +// controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR')); + } + }); } else if ('gnupg' === result[0]) { let params = { @@ -695,7 +707,8 @@ export class MailMessageView extends AbstractViewRight { pgpVerify(self) { if (self.pgpSigned()) { - const message = self.message(); + const message = self.message(), + sender = message && message.from[0].email; PgpUserStore.hasPublicKeyForEmails([message.from[0].email]).then(mode => { if ('gnupg' === mode) { let params = message.pgpSigned(); // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" } @@ -713,16 +726,15 @@ export class MailMessageView extends AbstractViewRight { }); } } else if ('openpgp' === mode) { - let text = null; - try { - // TODO: if message.pgpSigned().SigPartId then fetch raw from server - 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]); + const publicKey = OpenPGPUserStore.getPublicKeyFor(sender); + OpenPGPUserStore.verify(message.plain(), null/*detachedSignature*/, publicKey).then(result => { + if (result) { + // TODO: if result.data is not cleartext then + // parse mime to find and verify signature + message.plain(result.data); + message.viewPlain(); + console.dir({signatures:result.signatures}); + } /* if (validKey) { i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', { @@ -738,10 +750,7 @@ export class MailMessageView extends AbstractViewRight { i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE') + (additional ? ' (' + additional + ')' : ''); } */ - }); - } else { -// controlsHelper(dom, this, false, i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR')); - } + }); } }); }