#89 GnuPG verify cleartext messages

This commit is contained in:
the-djmaze 2022-02-03 12:05:33 +01:00
parent 73fa215e22
commit 45a714ee08
3 changed files with 78 additions and 54 deletions

View file

@ -177,19 +177,23 @@ export const PgpUserStore = new class {
} }
async verify(message) { async verify(message) {
const plain = message.plain(); const signed = message.pgpSigned();
if (/-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain)) { if (signed) {
return await OpenPGPUserStore.verify(message); const sender = message.from[0].email,
} gnupg = GnuPGUserStore.hasPublicKeyForEmails([sender]),
if (message.pgpSigned()) { openpgp = OpenPGPUserStore.hasPublicKeyForEmails([sender]);
const sender = message.from[0].email; // Detached signature use GnuPG first, else we must download whole message
let mode = await this.hasPublicKeyForEmails([sender]); if (gnupg && signed.SigPartId) {
if ('gnupg' === mode) {
return GnuPGUserStore.verify(message); return GnuPGUserStore.verify(message);
} }
if ('openpgp' === mode) { if (openpgp) {
return OpenPGPUserStore.verify(message); return OpenPGPUserStore.verify(message);
} }
if (gnupg) {
return GnuPGUserStore.verify(message);
}
// Mailvelope can't
// https://github.com/mailvelope/mailvelope/issues/434
} }
} }

View file

@ -673,22 +673,22 @@ trait Messages
$oImapClient->FolderExamine($sFolderName); $oImapClient->FolderExamine($sFolderName);
$aParts = [ $aParts = [
FetchType::BODY_PEEK.'['.$sBodyPartId.']' FetchType::BODY_PEEK.'['.$sBodyPartId.']',
];
if ($sSigPartId) {
// An empty section specification refers to the entire message, including the header. // An empty section specification refers to the entire message, including the header.
// But Dovecot does not return it with BODY.PEEK[1], so we also use BODY.PEEK[1.MIME]. // But Dovecot does not return it with BODY.PEEK[1], so we also use BODY.PEEK[1.MIME].
$aParts[] = FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]'; FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]'
];
if ($sSigPartId) {
$aParts[] = FetchType::BODY_PEEK.'['.$sSigPartId.']'; $aParts[] = FetchType::BODY_PEEK.'['.$sSigPartId.']';
} }
$oFetchResponse = $oImapClient->Fetch($aParts, $iUid, true)[0]; $oFetchResponse = $oImapClient->Fetch($aParts, $iUid, true)[0];
$sBodyMime = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]');
if ($sSigPartId) { if ($sSigPartId) {
$result = [ $result = [
'text' => \preg_replace('/\\R/s', "\r\n", 'text' => \preg_replace('/\\R/s', "\r\n",
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]') $sBodyMime . $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')
. $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')
), ),
'signature' => preg_replace('/[^\x00-\x7F]/', '', 'signature' => preg_replace('/[^\x00-\x7F]/', '',
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']') $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']')
@ -697,53 +697,64 @@ trait Messages
} else { } else {
// clearsigned text // clearsigned text
$result = [ $result = [
'text' => \preg_replace('/\\R/s', "\r\n", 'text' => $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']'),
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')
),
'signature' => '' 'signature' => ''
]; ];
$decode = (new \MailSo\Mime\HeaderCollection($sBodyMime))->ValueByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING);
if ('base64' === $decode) {
$result['text'] = \base64_decode($result['text']);
} else if ('quoted-printable' === $decode) {
$result['text'] = \quoted_printable_decode($result['text']);
}
} }
if ($this->GetActionParam('GnuPG', 1)) { if ($this->GetActionParam('GnuPG', 1)) {
$GPG = $this->GnuPG(); $GPG = $this->GnuPG();
if ($GPG) { if ($GPG) {
$info = $this->GnuPG()->verify($result['text'], $result['signature'])[0]; $info = $this->GnuPG()->verify($result['text'], $result['signature']);
// $info = $this->GnuPG()->verifyStream($fp, $result['signature'])[0]; // $info = $this->GnuPG()->verifyStream($fp, $result['signature']);
if (empty($info[0])) {
$result = false;
} else {
$info = $info[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))
]; ];
}
} else {
$result = false;
} }
} }

View file

@ -365,9 +365,18 @@ class GnuPG
*/ */
public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/ public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/
{ {
return $this->GnuPG $result = $this->GnuPG
? $this->GnuPG->verify($signed_text, $signature, $plaintext) ? $this->GnuPG->verify($signed_text, $signature ?: false, $plaintext)
: $this->GPG->verify($signed_text, $signature, $plaintext); : $this->GPG->verify($signed_text, $signature, $plaintext);
if (!$result) {
if ($this->GnuPG) {
\error_log('gnupg_verify() failed: ' . $this->GnuPG->geterror() . "\n\n{$signed_text}\n\n{$signature}" );
\error_log(\print_r($this->GnuPG->geterrorinfo(),1));
} else {
\error_log('GPG->verify() failed');
}
}
return $result;
} }
/** /**