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;
}
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
&& !empty($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()
}
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
&& !empty($this->aContentTypeParams['protocol'])
&& 'application/pgp-signature' === \strtolower(\trim($this->aContentTypeParams['protocol']))
// The multipart/signed body MUST consist of exactly two parts.
&& 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
@ -192,7 +211,7 @@ class BodyStructure implements \JsonSerializable
if (!$aParts) {
$gEncryptedParts = $this->SearchByContentType('multipart/encrypted');
foreach ($gEncryptedParts as $oPart) {
if ($oPart->IsPgpEncrypted() && $oPart->SubParts()[1]->isInline()) {
if ($oPart->isPgpEncrypted() && $oPart->SubParts()[1]->isInline()) {
return array($oPart->SubParts()[1]);
}
}
@ -242,7 +261,7 @@ class BodyStructure implements \JsonSerializable
{
return $this->SearchByCallback(function ($oItem, $oParent) {
// 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');
foreach ($gSignatureParts as $oPart) {
if ($oPart->IsPgpSigned()) {
if ($oPart->isPgpSigned()) {
// 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].
$aFetchItems[] = FetchType::BODY_PEEK.'['.$oPart->SubParts()[0]->PartID().'.MIME]';

View file

@ -65,9 +65,13 @@ class Message implements \JsonSerializable
$aThreadUnseenUIDs = [];
private ?array $DraftInfo = null;
private ?array $pgpSigned = null;
private ?array $pgpEncrypted = null;
private ?array $smimeSigned = null;
private ?array $smimeEncrypted = null;
private ?\MailSo\Mime\EmailCollection
$oFrom = null,
$oSender = null,
@ -309,26 +313,44 @@ class Message implements \JsonSerializable
if ($oBodyStructure) {
$gEncryptedParts = $oBodyStructure->SearchByContentType('multipart/encrypted');
foreach ($gEncryptedParts as $oPart) {
if ($oPart->IsPgpEncrypted()) {
if ($oPart->isPgpEncrypted()) {
$oMessage->pgpEncrypted = [
'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');
foreach ($gSignatureParts as $oPart) {
if (!$oPart->IsPgpSigned()) {
continue;
if ($oPart->isPgpSigned()) {
$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.
// 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;
}
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);
return $header
&& \preg_match('#multipart/signed.+protocol=["\']?application/pgp-signature#si', $header->FullValue())
// The multipart/signed body MUST consist of exactly two parts.
&& 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

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));
// $result['signatures'] = $oPart->SubParts[0];
}