From da79feeaeed45840c5efeef78ed7462286aec3be Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 2 Feb 2022 15:24:32 +0100 Subject: [PATCH] #89 verify PGP signatures using OpenPGP.js --- dev/Stores/User/OpenPGP.js | 41 ++++++++-- dev/Stores/User/Pgp.js | 26 +------ dev/View/Popup/Compose.js | 6 +- dev/View/User/MailBox/MessageView.js | 18 ++++- .../libraries/RainLoop/Actions/Messages.php | 74 ++++++++++--------- 5 files changed, 93 insertions(+), 72 deletions(-) diff --git a/dev/Stores/User/OpenPGP.js b/dev/Stores/User/OpenPGP.js index 07e809830..aae020852 100644 --- a/dev/Stores/User/OpenPGP.js +++ b/dev/Stores/User/OpenPGP.js @@ -7,6 +7,8 @@ import ko from 'ko'; import { arrayLength } from 'Common/Utils'; import { delegateRunOnDestroy } from 'Common/UtilsUser'; +import Remote from 'Remote/User/Fetch'; + import { showScreenPopup } from 'Knoin/Knoin'; import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey'; @@ -201,14 +203,37 @@ export const OpenPGPUserStore = new class { } } - async verify(message, detachedSignature, publicKey) { -// message.getSigningKeyIDs(); - return await openpgp.verify({ - message, - verificationKeys: publicKey, -// expectSigned: true, // !!detachedSignature - signature: detachedSignature - }); + /** + * https://docs.openpgpjs.org/#sign-and-verify-cleartext-messages + */ + async verify(message) { + const data = message.pgpSigned(), // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" } + publicKey = this.publicKeys().find(key => key.emails.includes(message.from[0].email)); + if (data && publicKey) { + data.Folder = message.folder; + data.Uid = message.uid; + data.GnuPG = 0; + let response = await Remote.post('MessagePgpVerify', null, data); + if (response) { + const signature = response.Result.signature + ? await openpgp.readSignature({ armoredSignature: response.Result.signature }) + : null; + const signedMessage = signature + ? await openpgp.createMessage({ text: response.Result.text }) + : await openpgp.readCleartextMessage({ cleartextMessage: response.Result.text }); +// (signature||signedMessage).getSigningKeyIDs(); + let result = await openpgp.verify({ + message: signedMessage, + verificationKeys: publicKey.key, +// expectSigned: true, // !!detachedSignature + signature: signature + }); + return { + fingerprint: publicKey.fingerprint, + success: result && !!result.signatures.length + }; + } + } } }; diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js index c5415ce93..5d4b989ff 100644 --- a/dev/Stores/User/Pgp.js +++ b/dev/Stores/User/Pgp.js @@ -180,6 +180,7 @@ export const PgpUserStore = new class { if (/-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain)) { let result = await OpenPGPUserStore.verify(plain); console.dir(result); + return; } if (message.pgpSigned()) { const sender = message.from[0].email; @@ -188,34 +189,11 @@ export const PgpUserStore = new class { return GnuPGUserStore.verify(message); } if ('openpgp' === mode) { - const publicKey = OpenPGPUserStore.getPublicKeyFor(sender); - OpenPGPUserStore.verify(plain, null/*detachedSignature*/, publicKey).then(result => { - if (result) { - message.plain(result.data); - message.viewPlain(); - console.dir({signatures:result.signatures}); - } -/* - 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 + ')' : ''); - } -*/ - }); + return OpenPGPUserStore.verify(message); } } } - /** * Creates an iframe with an editor for a new encrypted mail. * The iframe will be injected into the container identified by selector. diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 62aa1f300..a6c8f1a0a 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -457,9 +457,9 @@ class ComposePopupView extends AbstractViewPopup { data: params.Text, }; if ('openpgp' == sign) { - let privateKey; + let privateKey, sender = this.currentIdentity().email(); try { - const key = OpenPGPUserStore.getPrivateKeyFor(this.currentIdentity().email()); + const key = OpenPGPUserStore.getPrivateKeyFor(sender); if (key) { key.decrypt(window.prompt('Passphrase')); cfg.privateKey = privateKey = key; @@ -470,7 +470,7 @@ class ComposePopupView extends AbstractViewPopup { } if (!privateKey) { this.sendError(true); - this.sendErrorDesc(i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND')); + this.sendErrorDesc(i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND_FOR', { EMAIL: sender })); return; } } diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 4ff3efc83..6104f4d57 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -641,10 +641,26 @@ export class MailMessageView extends AbstractViewRight { pgpVerify(/*self, event*/) { const oMessage = currentMessage()/*, ctrl = event.target.closest('.openpgp-control')*/; PgpUserStore.verify(oMessage).then(result => { - console.dir({result:result}); if (result) { oMessage.pgpVerified(result); } +/* + if (result && result.success) { + 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/PGP_ERROR', { + ERROR: 'message' + }) + (additional ? ' (' + additional + ')' : ''); + } +*/ }); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php index c3f07dd76..e2141e366 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php @@ -700,45 +700,47 @@ trait Messages ]; } - $GPG = $this->GnuPG(); - if ($GPG) { - $info = $this->GnuPG()->verify($result['text'], $result['signature'])[0]; -// $info = $this->GnuPG()->verifyStream($fp, $result['signature'])[0]; + if ($this->GetActionParam('GnuPG', 1)) { + $GPG = $this->GnuPG(); + if ($GPG) { + $info = $this->GnuPG()->verify($result['text'], $result['signature'])[0]; +// $info = $this->GnuPG()->verifyStream($fp, $result['signature'])[0]; - /** - * https://code.woboq.org/qt5/include/gpg-error.h.html - * status: - 0 = GPG_ERR_NO_ERROR - 9 = GPG_ERR_NO_PUBKEY - 117440513 = General error - 117440520 = Bad signature - */ + /** + * https://code.woboq.org/qt5/include/gpg-error.h.html + * status: + 0 = GPG_ERR_NO_ERROR + 9 = GPG_ERR_NO_PUBKEY + 117440513 = General error + 117440520 = Bad signature + */ - $summary = [ - GNUPG_SIGSUM_VALID => 'The signature is fully valid.', - GNUPG_SIGSUM_GREEN => 'The signature is good but one might want to display some extra information. Check the other bits.', - GNUPG_SIGSUM_RED => 'The signature is bad. It might be useful to check other bits and display more information, i.e. a revoked certificate might not render a signature invalid when the message was received prior to the cause for the revocation.', - GNUPG_SIGSUM_KEY_REVOKED => 'The key or at least one certificate has been revoked.', - GNUPG_SIGSUM_KEY_EXPIRED => 'The key or one of the certificates has expired. It is probably a good idea to display the date of the expiration.', - GNUPG_SIGSUM_SIG_EXPIRED => 'The signature has expired.', - GNUPG_SIGSUM_KEY_MISSING => 'Can’t verify due to a missing key or certificate.', - GNUPG_SIGSUM_CRL_MISSING => 'The CRL (or an equivalent mechanism) is not available.', - GNUPG_SIGSUM_CRL_TOO_OLD => 'Available CRL is too old.', - GNUPG_SIGSUM_BAD_POLICY => 'A policy requirement was not met.', - GNUPG_SIGSUM_SYS_ERROR => 'A system error occurred.', -// GNUPG_SIGSUM_TOFU_CONFLICT = 'A TOFU conflict was detected.', - ]; + $summary = [ + GNUPG_SIGSUM_VALID => 'The signature is fully valid.', + GNUPG_SIGSUM_GREEN => 'The signature is good but one might want to display some extra information. Check the other bits.', + GNUPG_SIGSUM_RED => 'The signature is bad. It might be useful to check other bits and display more information, i.e. a revoked certificate might not render a signature invalid when the message was received prior to the cause for the revocation.', + GNUPG_SIGSUM_KEY_REVOKED => 'The key or at least one certificate has been revoked.', + GNUPG_SIGSUM_KEY_EXPIRED => 'The key or one of the certificates has expired. It is probably a good idea to display the date of the expiration.', + GNUPG_SIGSUM_SIG_EXPIRED => 'The signature has expired.', + GNUPG_SIGSUM_KEY_MISSING => 'Can’t verify due to a missing key or certificate.', + GNUPG_SIGSUM_CRL_MISSING => 'The CRL (or an equivalent mechanism) is not available.', + GNUPG_SIGSUM_CRL_TOO_OLD => 'Available CRL is too old.', + GNUPG_SIGSUM_BAD_POLICY => 'A policy requirement was not met.', + GNUPG_SIGSUM_SYS_ERROR => 'A system error occurred.', +// GNUPG_SIGSUM_TOFU_CONFLICT = 'A TOFU conflict was detected.', + ]; - // Verified, so no need to return $result['text'] and $result['signature'] - $result = [ - 'fingerprint' => $info['fingerprint'], - 'validity' => $info['validity'], - 'status' => $info['status'], - 'summary' => $info['summary'], - 'message' => \implode("\n", \array_filter($summary, function($k) use ($info) { - return $info['summary'] & $k; - }, ARRAY_FILTER_USE_KEY)) - ]; + // Verified, so no need to return $result['text'] and $result['signature'] + $result = [ + 'fingerprint' => $info['fingerprint'], + 'validity' => $info['validity'], + 'status' => $info['status'], + 'summary' => $info['summary'], + 'message' => \implode("\n", \array_filter($summary, function($k) use ($info) { + return $info['summary'] & $k; + }, ARRAY_FILTER_USE_KEY)) + ]; + } } return $this->DefaultResponse(__FUNCTION__, $result);