#89 verify PGP signatures using OpenPGP.js

This commit is contained in:
the-djmaze 2022-02-02 15:24:32 +01:00
parent 93db6e6e0e
commit da79feeaee
5 changed files with 93 additions and 72 deletions

View file

@ -7,6 +7,8 @@ import ko from 'ko';
import { arrayLength } from 'Common/Utils'; import { arrayLength } from 'Common/Utils';
import { delegateRunOnDestroy } from 'Common/UtilsUser'; import { delegateRunOnDestroy } from 'Common/UtilsUser';
import Remote from 'Remote/User/Fetch';
import { showScreenPopup } from 'Knoin/Knoin'; import { showScreenPopup } from 'Knoin/Knoin';
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey'; import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
@ -201,14 +203,37 @@ export const OpenPGPUserStore = new class {
} }
} }
async verify(message, detachedSignature, publicKey) { /**
// message.getSigningKeyIDs(); * https://docs.openpgpjs.org/#sign-and-verify-cleartext-messages
return await openpgp.verify({ */
message, async verify(message) {
verificationKeys: publicKey, const data = message.pgpSigned(), // { BodyPartId: "1", SigPartId: "2", MicAlg: "pgp-sha256" }
// expectSigned: true, // !!detachedSignature publicKey = this.publicKeys().find(key => key.emails.includes(message.from[0].email));
signature: detachedSignature 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
};
}
}
} }
}; };

View file

@ -180,6 +180,7 @@ export const PgpUserStore = new class {
if (/-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain)) { if (/-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain)) {
let result = await OpenPGPUserStore.verify(plain); let result = await OpenPGPUserStore.verify(plain);
console.dir(result); console.dir(result);
return;
} }
if (message.pgpSigned()) { if (message.pgpSigned()) {
const sender = message.from[0].email; const sender = message.from[0].email;
@ -188,34 +189,11 @@ export const PgpUserStore = new class {
return GnuPGUserStore.verify(message); return GnuPGUserStore.verify(message);
} }
if ('openpgp' === mode) { if ('openpgp' === mode) {
const publicKey = OpenPGPUserStore.getPublicKeyFor(sender); return OpenPGPUserStore.verify(message);
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 + ')' : '');
}
*/
});
} }
} }
} }
/** /**
* Creates an iframe with an editor for a new encrypted mail. * Creates an iframe with an editor for a new encrypted mail.
* The iframe will be injected into the container identified by selector. * The iframe will be injected into the container identified by selector.

View file

@ -457,9 +457,9 @@ class ComposePopupView extends AbstractViewPopup {
data: params.Text, data: params.Text,
}; };
if ('openpgp' == sign) { if ('openpgp' == sign) {
let privateKey; let privateKey, sender = this.currentIdentity().email();
try { try {
const key = OpenPGPUserStore.getPrivateKeyFor(this.currentIdentity().email()); const key = OpenPGPUserStore.getPrivateKeyFor(sender);
if (key) { if (key) {
key.decrypt(window.prompt('Passphrase')); key.decrypt(window.prompt('Passphrase'));
cfg.privateKey = privateKey = key; cfg.privateKey = privateKey = key;
@ -470,7 +470,7 @@ class ComposePopupView extends AbstractViewPopup {
} }
if (!privateKey) { if (!privateKey) {
this.sendError(true); 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; return;
} }
} }

View file

@ -641,10 +641,26 @@ export class MailMessageView extends AbstractViewRight {
pgpVerify(/*self, event*/) { pgpVerify(/*self, event*/) {
const oMessage = currentMessage()/*, ctrl = event.target.closest('.openpgp-control')*/; const oMessage = currentMessage()/*, ctrl = event.target.closest('.openpgp-control')*/;
PgpUserStore.verify(oMessage).then(result => { PgpUserStore.verify(oMessage).then(result => {
console.dir({result:result});
if (result) { if (result) {
oMessage.pgpVerified(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 + ')' : '');
}
*/
}); });
} }

View file

@ -700,45 +700,47 @@ trait Messages
]; ];
} }
$GPG = $this->GnuPG(); if ($this->GetActionParam('GnuPG', 1)) {
if ($GPG) { $GPG = $this->GnuPG();
$info = $this->GnuPG()->verify($result['text'], $result['signature'])[0]; if ($GPG) {
// $info = $this->GnuPG()->verifyStream($fp, $result['signature'])[0]; $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 * https://code.woboq.org/qt5/include/gpg-error.h.html
* status: * status:
0 = GPG_ERR_NO_ERROR 0 = GPG_ERR_NO_ERROR
9 = GPG_ERR_NO_PUBKEY 9 = GPG_ERR_NO_PUBKEY
117440513 = General error 117440513 = General error
117440520 = Bad signature 117440520 = Bad signature
*/ */
$summary = [ $summary = [
GNUPG_SIGSUM_VALID => 'The signature is fully valid.', 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_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_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_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_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_SIG_EXPIRED => 'The signature has expired.',
GNUPG_SIGSUM_KEY_MISSING => 'Cant verify due to a missing key or certificate.', GNUPG_SIGSUM_KEY_MISSING => 'Cant verify due to a missing key or certificate.',
GNUPG_SIGSUM_CRL_MISSING => 'The CRL (or an equivalent mechanism) is not available.', 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_CRL_TOO_OLD => 'Available CRL is too old.',
GNUPG_SIGSUM_BAD_POLICY => 'A policy requirement was not met.', GNUPG_SIGSUM_BAD_POLICY => 'A policy requirement was not met.',
GNUPG_SIGSUM_SYS_ERROR => 'A system error occurred.', GNUPG_SIGSUM_SYS_ERROR => 'A system error occurred.',
// GNUPG_SIGSUM_TOFU_CONFLICT = 'A TOFU conflict was detected.', // GNUPG_SIGSUM_TOFU_CONFLICT = 'A TOFU conflict was detected.',
]; ];
// Verified, so no need to return $result['text'] and $result['signature'] // Verified, so no need to return $result['text'] and $result['signature']
$result = [ $result = [
'fingerprint' => $info['fingerprint'], 'fingerprint' => $info['fingerprint'],
'validity' => $info['validity'], 'validity' => $info['validity'],
'status' => $info['status'], 'status' => $info['status'],
'summary' => $info['summary'], 'summary' => $info['summary'],
'message' => \implode("\n", \array_filter($summary, function($k) use ($info) { 'message' => \implode("\n", \array_filter($summary, function($k) use ($info) {
return $info['summary'] & $k; return $info['summary'] & $k;
}, ARRAY_FILTER_USE_KEY)) }, ARRAY_FILTER_USE_KEY))
]; ];
}
} }
return $this->DefaultResponse(__FUNCTION__, $result); return $this->DefaultResponse(__FUNCTION__, $result);