mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
#89 GnuPG verify cleartext messages
This commit is contained in:
parent
73fa215e22
commit
45a714ee08
3 changed files with 78 additions and 54 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 => 'Can’t verify due to a missing key or certificate.',
|
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_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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue