mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
#89 Detect and verify PGP cleartext/clearsigned messages
This commit is contained in:
parent
d35473841f
commit
b8d898bc8a
2 changed files with 46 additions and 42 deletions
|
|
@ -77,8 +77,6 @@ class Message implements \JsonSerializable
|
||||||
|
|
||||||
$aThreads = array(),
|
$aThreads = array(),
|
||||||
|
|
||||||
$bTextPartIsTrimmed = false,
|
|
||||||
|
|
||||||
$aPgpSigned = null,
|
$aPgpSigned = null,
|
||||||
$bPgpEncrypted = false;
|
$bPgpEncrypted = false;
|
||||||
|
|
||||||
|
|
@ -538,27 +536,28 @@ class Message implements \JsonSerializable
|
||||||
$sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']');
|
$sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']');
|
||||||
if (null === $sText)
|
if (null === $sText)
|
||||||
{
|
{
|
||||||
|
// TextPartIsTrimmed ?
|
||||||
$sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']<0>');
|
$sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']<0>');
|
||||||
if (\is_string($sText) && \strlen($sText))
|
|
||||||
{
|
|
||||||
$oMessage->bTextPartIsTrimmed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_string($sText) && \strlen($sText))
|
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::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);
|
$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())
|
if ('text/html' === $oPart->ContentType())
|
||||||
{
|
{
|
||||||
$aHtmlParts[] = $sText;
|
$aHtmlParts[] = $sText;
|
||||||
|
|
@ -578,19 +577,6 @@ class Message implements \JsonSerializable
|
||||||
$oMessage->sHtml = \implode('<br />', $aHtmlParts);
|
$oMessage->sHtml = \implode('<br />', $aHtmlParts);
|
||||||
$oMessage->sPlain = \trim(\implode("\n", $aPlainParts));
|
$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-----');
|
$oMessage->bPgpEncrypted = !$oMessage->bPgpEncrypted && false !== \stripos($oMessage->sPlain, '-----BEGIN PGP MESSAGE-----');
|
||||||
|
|
||||||
unset($aHtmlParts, $aPlainParts, $aMatch);
|
unset($aHtmlParts, $aPlainParts, $aMatch);
|
||||||
|
|
@ -602,6 +588,7 @@ class Message implements \JsonSerializable
|
||||||
$oMessage->oAttachments = new AttachmentCollection;
|
$oMessage->oAttachments = new AttachmentCollection;
|
||||||
foreach ($gAttachmentsParts as /* @var $oAttachmentItem \MailSo\Imap\BodyStructure */ $oAttachmentItem)
|
foreach ($gAttachmentsParts as /* @var $oAttachmentItem \MailSo\Imap\BodyStructure */ $oAttachmentItem)
|
||||||
{
|
{
|
||||||
|
// if ('application/pgp-keys' === $oAttachmentItem->ContentType()) import ???
|
||||||
$oMessage->oAttachments->append(
|
$oMessage->oAttachments->append(
|
||||||
Attachment::NewBodyStructureInstance($oMessage->sFolder, $oMessage->iUid, $oAttachmentItem)
|
Attachment::NewBodyStructureInstance($oMessage->sFolder, $oMessage->iUid, $oAttachmentItem)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -652,6 +652,9 @@ trait Messages
|
||||||
return $this->DefaultResponse(__FUNCTION__, $mResult);
|
return $this->DefaultResponse(__FUNCTION__, $mResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://datatracker.ietf.org/doc/html/rfc3156#section-5
|
||||||
|
*/
|
||||||
public function DoMessagePgpVerify() : array
|
public function DoMessagePgpVerify() : array
|
||||||
{
|
{
|
||||||
$sFolderName = $this->GetActionParam('Folder', '');
|
$sFolderName = $this->GetActionParam('Folder', '');
|
||||||
|
|
@ -665,14 +668,19 @@ trait Messages
|
||||||
$oImapClient = $this->MailClient()->ImapClient();
|
$oImapClient = $this->MailClient()->ImapClient();
|
||||||
$oImapClient->FolderExamine($sFolderName);
|
$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.
|
// 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].
|
||||||
FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]',
|
$aParts[] = FetchType::BODY_PEEK.'['.$sBodyPartId.'.MIME]';
|
||||||
FetchType::BODY_PEEK.'['.$sBodyPartId.']',
|
$aParts[] = FetchType::BODY_PEEK.'['.$sSigPartId.']';
|
||||||
FetchType::BODY_PEEK.'['.$sSigPartId.']'
|
}
|
||||||
], $iUid, true)[0];
|
|
||||||
|
|
||||||
|
$oFetchResponse = $oImapClient->Fetch($aParts, $iUid, true)[0];
|
||||||
|
|
||||||
|
if ($sSigPartId) {
|
||||||
$result = [
|
$result = [
|
||||||
'Text' => \preg_replace('/\\R/s', "\r\n",
|
'Text' => \preg_replace('/\\R/s', "\r\n",
|
||||||
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]')
|
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sBodyPartId.'.MIME]')
|
||||||
|
|
@ -682,6 +690,15 @@ trait Messages
|
||||||
$oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']')
|
$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')) {
|
if (\class_exists('gnupg')) {
|
||||||
$info = $this->GnuPG()->verify($result['Text'], $result['Signature'])[0];
|
$info = $this->GnuPG()->verify($result['Text'], $result['Signature'])[0];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue