Added isSMimeEncrypted() and isSMimeSigned() #259

This commit is contained in:
the-djmaze 2024-02-14 02:43:54 +01:00
parent 3648b3eb60
commit 7b3102a6b3
5 changed files with 77 additions and 27 deletions

View file

@ -127,9 +127,9 @@ class BodyStructure implements \JsonSerializable
return 'text/html' === $this->sContentType || 'text/plain' === $this->sContentType; return 'text/html' === $this->sContentType || 'text/plain' === $this->sContentType;
} }
public function IsPgpEncrypted() : bool // https://datatracker.ietf.org/doc/html/rfc3156#section-4
public function isPgpEncrypted() : bool
{ {
// https://datatracker.ietf.org/doc/html/rfc3156#section-4
return 'multipart/encrypted' === $this->sContentType return 'multipart/encrypted' === $this->sContentType
&& !empty($this->aContentTypeParams['protocol']) && !empty($this->aContentTypeParams['protocol'])
&& 'application/pgp-encrypted' === \strtolower(\trim($this->aContentTypeParams['protocol'])) && 'application/pgp-encrypted' === \strtolower(\trim($this->aContentTypeParams['protocol']))
@ -140,20 +140,39 @@ class BodyStructure implements \JsonSerializable
// && 'Version: 1' === $this->aSubParts[0]->Body() // && 'Version: 1' === $this->aSubParts[0]->Body()
} }
public function IsPgpSigned() : bool // https://datatracker.ietf.org/doc/html/rfc3156#section-5
public function isPgpSigned() : bool
{ {
// https://datatracker.ietf.org/doc/html/rfc3156#section-5
return 'multipart/signed' === $this->sContentType return 'multipart/signed' === $this->sContentType
&& !empty($this->aContentTypeParams['protocol']) && !empty($this->aContentTypeParams['protocol'])
&& 'application/pgp-signature' === \strtolower(\trim($this->aContentTypeParams['protocol'])) && 'application/pgp-signature' === \strtolower(\trim($this->aContentTypeParams['protocol']))
// The multipart/signed body MUST consist of exactly two parts. // The multipart/signed body MUST consist of exactly two parts.
&& 2 === \count($this->aSubParts) && 2 === \count($this->aSubParts)
&& $this->aSubParts[1]->IsPgpSignature(); && 'application/pgp-signature' === $this->aSubParts[1]->ContentType();
} }
public function IsPgpSignature() : bool // https://datatracker.ietf.org/doc/html/rfc2633#section-3.3
public function isSMimeEncrypted() : bool
{ {
return \in_array($this->sContentType, ['application/pgp-signature', 'application/pkcs7-signature']); $type = \strtolower(\trim($this->aContentTypeParams['smime-type'] ?? ''));
return 'application/pkcs7-mime' === $this->sContentType
&& !empty($this->aContentTypeParams['smime-type'])
&& ('enveloped-data' === $type || 'authenveloped-data' === $type);
}
// https://www.rfc-editor.org/rfc/rfc8551.html#section-3.5
public function isSMimeSigned() : bool
{
return ('multipart/signed' === $this->sContentType
&& !empty($this->aContentTypeParams['protocol'])
&& 'application/pkcs7-signature' === \strtolower(\trim($this->aContentTypeParams['protocol']))
// The multipart/signed body MUST consist of exactly two parts.
&& 2 === \count($this->aSubParts)
&& 'application/pkcs7-signature' === $this->aSubParts[1]->ContentType()
) || ('application/pkcs7-mime' === $this->sContentType
&& !empty($this->aContentTypeParams['smime-type'])
&& 'signed-data' === \strtolower(\trim($this->aContentTypeParams['smime-type']))
);
} }
public function IsAttachment() : bool public function IsAttachment() : bool
@ -192,7 +211,7 @@ class BodyStructure implements \JsonSerializable
if (!$aParts) { if (!$aParts) {
$gEncryptedParts = $this->SearchByContentType('multipart/encrypted'); $gEncryptedParts = $this->SearchByContentType('multipart/encrypted');
foreach ($gEncryptedParts as $oPart) { foreach ($gEncryptedParts as $oPart) {
if ($oPart->IsPgpEncrypted() && $oPart->SubParts()[1]->isInline()) { if ($oPart->isPgpEncrypted() && $oPart->SubParts()[1]->isInline()) {
return array($oPart->SubParts()[1]); return array($oPart->SubParts()[1]);
} }
} }
@ -242,7 +261,7 @@ class BodyStructure implements \JsonSerializable
{ {
return $this->SearchByCallback(function ($oItem, $oParent) { return $this->SearchByCallback(function ($oItem, $oParent) {
// return $oItem->IsAttachment(); // return $oItem->IsAttachment();
return $oItem->IsAttachment() && (!$oParent || !$oParent->IsPgpEncrypted()); return $oItem->IsAttachment() && (!$oParent || !$oParent->isPgpEncrypted());
}); });
} }

View file

@ -152,7 +152,7 @@ class MailClient
/* /*
$gSignatureParts = $oBodyStructure->SearchByContentType('multipart/signed'); $gSignatureParts = $oBodyStructure->SearchByContentType('multipart/signed');
foreach ($gSignatureParts as $oPart) { foreach ($gSignatureParts as $oPart) {
if ($oPart->IsPgpSigned()) { if ($oPart->isPgpSigned()) {
// 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].
$aFetchItems[] = FetchType::BODY_PEEK.'['.$oPart->SubParts()[0]->PartID().'.MIME]'; $aFetchItems[] = FetchType::BODY_PEEK.'['.$oPart->SubParts()[0]->PartID().'.MIME]';

View file

@ -65,9 +65,13 @@ class Message implements \JsonSerializable
$aThreadUnseenUIDs = []; $aThreadUnseenUIDs = [];
private ?array $DraftInfo = null; private ?array $DraftInfo = null;
private ?array $pgpSigned = null; private ?array $pgpSigned = null;
private ?array $pgpEncrypted = null; private ?array $pgpEncrypted = null;
private ?array $smimeSigned = null;
private ?array $smimeEncrypted = null;
private ?\MailSo\Mime\EmailCollection private ?\MailSo\Mime\EmailCollection
$oFrom = null, $oFrom = null,
$oSender = null, $oSender = null,
@ -309,26 +313,44 @@ class Message implements \JsonSerializable
if ($oBodyStructure) { if ($oBodyStructure) {
$gEncryptedParts = $oBodyStructure->SearchByContentType('multipart/encrypted'); $gEncryptedParts = $oBodyStructure->SearchByContentType('multipart/encrypted');
foreach ($gEncryptedParts as $oPart) { foreach ($gEncryptedParts as $oPart) {
if ($oPart->IsPgpEncrypted()) { if ($oPart->isPgpEncrypted()) {
$oMessage->pgpEncrypted = [ $oMessage->pgpEncrypted = [
'partId' => $oPart->SubParts()[1]->PartID() 'partId' => $oPart->SubParts()[1]->PartID()
]; ];
} }
} }
$gEncryptedParts = $oBodyStructure->SearchByContentType('application/pkcs7-mime');
foreach ($gEncryptedParts as $oPart) {
if ($oPart->isSMimeEncrypted()) {
$oMessage->smimeEncrypted = [
'partId' => $oPart->PartID()
];
} else if ($oPart->isSMimeSigned()) {
$oMessage->smimeSigned = [
'sigPartId' => $oPart->PartID(),
'micAlg' => $oHeaders ? (string) $oHeaders->ParameterValue(MimeHeader::CONTENT_TYPE, 'micalg') : ''
];
}
}
$gSignatureParts = $oBodyStructure->SearchByContentType('multipart/signed'); $gSignatureParts = $oBodyStructure->SearchByContentType('multipart/signed');
foreach ($gSignatureParts as $oPart) { foreach ($gSignatureParts as $oPart) {
if (!$oPart->IsPgpSigned()) { if ($oPart->isPgpSigned()) {
continue; $oMessage->pgpSigned = [
// /?/Raw/&q[]=/0/Download/&q[]=/...
// /?/Raw/&q[]=/0/View/&q[]=/...
'bodyPartId' => $oPart->SubParts()[0]->PartID(),
'sigPartId' => $oPart->SubParts()[1]->PartID(),
'micAlg' => $oHeaders ? (string) $oHeaders->ParameterValue(MimeHeader::CONTENT_TYPE, 'micalg') : ''
];
} else if ($oPart->isSMimeSigned()) {
$oMessage->smimeSigned = [
'bodyPartId' => $oPart->SubParts()[0]->PartID(),
'sigPartId' => $oPart->SubParts()[1]->PartID(),
'micAlg' => $oHeaders ? (string) $oHeaders->ParameterValue(MimeHeader::CONTENT_TYPE, 'micalg') : ''
];
} }
$oPgpSignaturePart = $oPart->SubParts()[1];
$oMessage->pgpSigned = [
// /?/Raw/&q[]=/0/Download/&q[]=/...
// /?/Raw/&q[]=/0/View/&q[]=/...
'bodyPartId' => $oPart->SubParts()[0]->PartID(),
'sigPartId' => $oPgpSignaturePart->PartID(),
'micAlg' => $oHeaders ? (string) $oHeaders->ParameterValue(MimeHeader::CONTENT_TYPE, 'micalg') : ''
];
/* /*
// 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].

View file

@ -80,20 +80,29 @@ class Part
return $sResult; return $sResult;
} }
public function IsPgpSigned() : bool // https://datatracker.ietf.org/doc/html/rfc3156#section-5
public function isPgpSigned() : bool
{ {
// https://datatracker.ietf.org/doc/html/rfc3156#section-5
$header = $this->Headers->GetByName(Enumerations\Header::CONTENT_TYPE); $header = $this->Headers->GetByName(Enumerations\Header::CONTENT_TYPE);
return $header return $header
&& \preg_match('#multipart/signed.+protocol=["\']?application/pgp-signature#si', $header->FullValue()) && \preg_match('#multipart/signed.+protocol=["\']?application/pgp-signature#si', $header->FullValue())
// The multipart/signed body MUST consist of exactly two parts. // The multipart/signed body MUST consist of exactly two parts.
&& 2 === \count($this->SubParts) && 2 === \count($this->SubParts)
&& $this->SubParts[1]->IsPgpSignature(); && 'application/pgp-signature' === $this->SubParts[1]->ContentType();
} }
public function IsPgpSignature() : bool // https://www.rfc-editor.org/rfc/rfc8551.html#section-3.5
public function isSMimeSigned() : bool
{ {
return \in_array($this->ContentType(), array('application/pgp-signature', 'application/pkcs7-signature')); $header = $this->Headers->GetByName(Enumerations\Header::CONTENT_TYPE);
return ($header
&& \preg_match('#multipart/signed.+protocol=["\']?application/pkcs7-signature#si', $header->FullValue())
// The multipart/signed body MUST consist of exactly two parts.
&& 2 === \count($this->SubParts)
&& 'application/pkcs7-signature' === $this->SubParts[1]->ContentType()
) || ($header
&& \preg_match('#application/pkcs7-mime.+smime-type=["\']?signed-data#si', $header->FullValue())
);
} }
public static function FromFile(string $sFileName) : ?self public static function FromFile(string $sFileName) : ?self

View file

@ -146,7 +146,7 @@ trait Pgp
); );
} }
if ($oPart && $oPart->IsPgpSigned()) { if ($oPart && $oPart->isPgpSigned()) {
// $GPG->verifyStream($oPart->SubParts[0]->Body, \stream_get_contents($oPart->SubParts[1]->Body)); // $GPG->verifyStream($oPart->SubParts[0]->Body, \stream_get_contents($oPart->SubParts[1]->Body));
// $result['signatures'] = $oPart->SubParts[0]; // $result['signatures'] = $oPart->SubParts[0];
} }