#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) {
const plain = message.plain();
if (/-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain)) {
return await OpenPGPUserStore.verify(message);
}
if (message.pgpSigned()) {
const sender = message.from[0].email;
let mode = await this.hasPublicKeyForEmails([sender]);
if ('gnupg' === mode) {
const signed = message.pgpSigned();
if (signed) {
const sender = message.from[0].email,
gnupg = GnuPGUserStore.hasPublicKeyForEmails([sender]),
openpgp = OpenPGPUserStore.hasPublicKeyForEmails([sender]);
// Detached signature use GnuPG first, else we must download whole message
if (gnupg && signed.SigPartId) {
return GnuPGUserStore.verify(message);
}
if ('openpgp' === mode) {
if (openpgp) {
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);
$aParts = [
FetchType::BODY_PEEK.'['.$sBodyPartId.']'
];
if ($sSigPartId) {
FetchType::BODY_PEEK.'['.$sBodyPartId.']',
// 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].
$aParts[] = FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]';
FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]'
];
if ($sSigPartId) {
$aParts[] = FetchType::BODY_PEEK.'['.$sSigPartId.']';
}
$oFetchResponse = $oImapClient->Fetch($aParts, $iUid, true)[0];
$sBodyMime = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]');
if ($sSigPartId) {
$result = [
'text' => \preg_replace('/\\R/s', "\r\n",
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]')
. $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')
$sBodyMime . $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')
),
'signature' => preg_replace('/[^\x00-\x7F]/', '',
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']')
@ -697,53 +697,64 @@ trait Messages
} else {
// clearsigned text
$result = [
'text' => \preg_replace('/\\R/s', "\r\n",
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')
),
'text' => $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']'),
'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)) {
$GPG = $this->GnuPG();
if ($GPG) {
$info = $this->GnuPG()->verify($result['text'], $result['signature'])[0];
// $info = $this->GnuPG()->verifyStream($fp, $result['signature'])[0];
$info = $this->GnuPG()->verify($result['text'], $result['signature']);
// $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
* 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 => '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_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 => '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_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))
];
}
} else {
$result = false;
}
}

View file

@ -365,9 +365,18 @@ class GnuPG
*/
public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/
{
return $this->GnuPG
? $this->GnuPG->verify($signed_text, $signature, $plaintext)
$result = $this->GnuPG
? $this->GnuPG->verify($signed_text, $signature ?: false, $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;
}
/**