#89 Detect and verify PGP cleartext/clearsigned messages

This commit is contained in:
the-djmaze 2022-01-18 14:51:08 +01:00
parent d35473841f
commit b8d898bc8a
2 changed files with 46 additions and 42 deletions

View file

@ -77,8 +77,6 @@ class Message implements \JsonSerializable
$aThreads = array(),
$bTextPartIsTrimmed = false,
$aPgpSigned = null,
$bPgpEncrypted = false;
@ -538,27 +536,28 @@ class Message implements \JsonSerializable
$sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']');
if (null === $sText)
{
// TextPartIsTrimmed ?
$sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']<0>');
if (\is_string($sText) && \strlen($sText))
{
$oMessage->bTextPartIsTrimmed = true;
}
}
if (\is_string($sText) && \strlen($sText))
{
$sTextCharset = $oPart->Charset();
if (empty($sTextCharset))
{
$sTextCharset = $sCharset;
}
$sTextCharset = Utils::NormalizeCharset($sTextCharset, true);
$sText = Utils::DecodeEncodingValue($sText, $oPart->MailEncodingName());
$sText = Utils::ConvertEncoding($sText, $sTextCharset, \MailSo\Base\Enumerations\Charset::UTF_8);
$sText = Utils::ConvertEncoding($sText,
Utils::NormalizeCharset($oPart->Charset() ?: $sCharset, true),
\MailSo\Base\Enumerations\Charset::UTF_8
);
$sText = Utils::Utf8Clear($sText);
// https://datatracker.ietf.org/doc/html/rfc4880#section-7
// Cleartext Signature
if (!$oMessage->aPgpSigned && \str_contains($sText, '-----BEGIN PGP SIGNED MESSAGE-----'))
{
$oMessage->aPgpSigned = [
'BodyPartId' => $oPart->PartID()
];
}
if ('text/html' === $oPart->ContentType())
{
$aHtmlParts[] = $sText;
@ -578,19 +577,6 @@ class Message implements \JsonSerializable
$oMessage->sHtml = \implode('<br />', $aHtmlParts);
$oMessage->sPlain = \trim(\implode("\n", $aPlainParts));
$aMatch = array();
if (!$oMessage->aPgpSigned && \preg_match('/-----BEGIN PGP SIGNATURE-----.+?-----END PGP SIGNATURE-----/ism', $oMessage->sPlain, $aMatch))
{
$oMessage->aPgpSigned = [
// /?/Raw/&q[]=/0/Download/&q[]=/...
// /?/Raw/&q[]=/0/View/&q[]=/...
'BodyPartId' => 0,
'SigPartId' => 0,
'MicAlg' => '',
'Signature' => \trim($aMatch[0])
];
}
$oMessage->bPgpEncrypted = !$oMessage->bPgpEncrypted && false !== \stripos($oMessage->sPlain, '-----BEGIN PGP MESSAGE-----');
unset($aHtmlParts, $aPlainParts, $aMatch);
@ -602,6 +588,7 @@ class Message implements \JsonSerializable
$oMessage->oAttachments = new AttachmentCollection;
foreach ($gAttachmentsParts as /* @var $oAttachmentItem \MailSo\Imap\BodyStructure */ $oAttachmentItem)
{
// if ('application/pgp-keys' === $oAttachmentItem->ContentType()) import ???
$oMessage->oAttachments->append(
Attachment::NewBodyStructureInstance($oMessage->sFolder, $oMessage->iUid, $oAttachmentItem)
);

View file

@ -652,6 +652,9 @@ trait Messages
return $this->DefaultResponse(__FUNCTION__, $mResult);
}
/**
* https://datatracker.ietf.org/doc/html/rfc3156#section-5
*/
public function DoMessagePgpVerify() : array
{
$sFolderName = $this->GetActionParam('Folder', '');
@ -665,23 +668,37 @@ trait Messages
$oImapClient = $this->MailClient()->ImapClient();
$oImapClient->FolderExamine($sFolderName);
$oFetchResponse = $oImapClient->Fetch([
$aParts = [
FetchType::BODY_PEEK.'['.$sBodyPartId.']'
];
if ($sSigPartId) {
// 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].
FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]',
FetchType::BODY_PEEK.'['.$sBodyPartId.']',
FetchType::BODY_PEEK.'['.$sSigPartId.']'
], $iUid, true)[0];
$aParts[] = FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]';
$aParts[] = FetchType::BODY_PEEK.'['.$sSigPartId.']';
}
$result = [
'Text' => \preg_replace('/\\R/s', "\r\n",
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]')
. $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')
),
'Signature' => preg_replace('/[^\x00-\x7F]/', '',
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']')
)
];
$oFetchResponse = $oImapClient->Fetch($aParts, $iUid, true)[0];
if ($sSigPartId) {
$result = [
'Text' => \preg_replace('/\\R/s', "\r\n",
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]')
. $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')
),
'Signature' => preg_replace('/[^\x00-\x7F]/', '',
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']')
)
];
} else {
// clearsigned text
$result = [
'Text' => \preg_replace('/\\R/s', "\r\n",
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.']')
),
'Signature' => false
];
}
if (\class_exists('gnupg')) {
$info = $this->GnuPG()->verify($result['Text'], $result['Signature'])[0];