From 870019d2dfabb25fc11ed233d489b281f827610c Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 4 Mar 2024 01:07:00 +0100 Subject: [PATCH] Automatically verify PGP and S/MIME signed messages --- dev/Model/Message.js | 9 +-- dev/View/User/MailBox/MessageView.js | 25 ++++---- .../app/libraries/MailSo/Mail/MailClient.php | 25 +------- .../app/libraries/MailSo/Mail/Message.php | 2 +- .../libraries/RainLoop/Actions/Messages.php | 58 +++++++++++++++++++ .../app/libraries/RainLoop/Actions/Pgp.php | 2 +- .../app/libraries/snappymail/pgp/gnupg.php | 2 +- .../templates/Views/User/MailMessageView.html | 6 +- 8 files changed, 82 insertions(+), 47 deletions(-) diff --git a/dev/Model/Message.js b/dev/Model/Message.js index 90c1b0949..ddec95a0c 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -120,12 +120,10 @@ export class MessageModel extends AbstractModel { encrypted: false, pgpSigned: null, - pgpVerified: null, pgpEncrypted: null, pgpDecrypted: false, smimeSigned: null, - smimeVerified: null, smimeEncrypted: null, smimeDecrypted: false, @@ -199,10 +197,9 @@ export class MessageModel extends AbstractModel { } }); - this.smimeSigned.subscribe(value => { - value?.body && MimeToMessage(value.body, this); - 'verified' in value && this.smimeVerified(value.verified); - }); + this.smimeSigned.subscribe(value => + value?.body && MimeToMessage(value.body, this) + ); } get requestHash() { diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 51266c971..001f2d6ba 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -583,8 +583,7 @@ export class MailMessageView extends AbstractViewRight { MimeToMessage(result.data, oMessage); oMessage.html() ? oMessage.viewHtml() : oMessage.viewPlain(); if (result.signatures?.length) { - oMessage.pgpSigned(true); - oMessage.pgpVerified({ + oMessage.pgpSigned({ signatures: result.signatures, success: !!result.signatures.length }); @@ -603,7 +602,7 @@ export class MailMessageView extends AbstractViewRight { const oMessage = currentMessage()/*, ctrl = event.target.closest('.openpgp-control')*/; PgpUserStore.verify(oMessage).then(result => { if (result) { - oMessage.pgpVerified(result); + oMessage.pgpSigned(result); } else { alert('Verification failed or no valid public key found'); } @@ -668,22 +667,22 @@ export class MailMessageView extends AbstractViewRight { } smimeVerify(/*self, event*/) { - const message = currentMessage(); - let data = message.smimeSigned(); // { partId: "1", micAlg: "pgp-sha256" } + const message = currentMessage(), + data = message.smimeSigned(); // { partId: "1", micAlg: "pgp-sha256" } if (data) { - data = { ...data }; // clone - data.folder = message.folder; - data.uid = message.uid; - data.bodyPart = data.bodyPart?.raw; - data.sigPart = data.sigPart?.bodyRaw; - Remote.post('SMimeVerifyMessage', null, data).then(response => { + const params = { ...data }; // clone + params.folder = message.folder; + params.uid = message.uid; + params.bodyPart = data.bodyPart?.raw; + params.sigPart = data.sigPart?.bodyRaw; + Remote.post('SMimeVerifyMessage', null, params).then(response => { if (response?.Result) { if (response.Result.body) { MimeToMessage(response.Result.body, message); message.html() ? message.viewHtml() : message.viewPlain(); - response.Result.body = null; } - message.smimeVerified(response.Result.success); + data.success = response.Result.success; + message.smimeSigned(data); } }); } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php index 2f743d71d..f95d586e6 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php @@ -126,7 +126,6 @@ class MailClient $this->oImapClient->FolderExamine($sFolderName); $oBodyStructure = null; - $oMessage = null; $aFetchItems = array( FetchType::UID, @@ -169,28 +168,10 @@ class MailClient } $aFetchResponse = $this->oImapClient->Fetch($aFetchItems, $iIndex, $bIndexIsUid); - if (\count($aFetchResponse)) { - $oMessage = Message::fromFetchResponse($sFolderName, $aFetchResponse[0], $oBodyStructure); - // S/MIME signed. Verify it, so we have the raw mime body to show - if ($oMessage->smimeSigned) try { - $bOpaque = !$oMessage->smimeSigned['detached']; - $sBody = $this->oImapClient->FetchMessagePart( - $oMessage->Uid, - $oMessage->smimeSigned['partId'] - ); - $result = (new \SnappyMail\SMime\OpenSSL(''))->verify($sBody, null, $bOpaque); - if ($result) { - if ($bOpaque) { - $oMessage->smimeSigned['body'] = $result['body']; - } - $oMessage->smimeSigned['verified'] = $result['success']; - } - } catch (\Throwable $e) { - $this->logException($e); - } - } - return $oMessage; + return \count($aFetchResponse) + ? Message::fromFetchResponse($sFolderName, $aFetchResponse[0], $oBodyStructure) + : null; } /** diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php index 295fc1fd3..c64d660f6 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php @@ -66,7 +66,7 @@ class Message implements \JsonSerializable private ?array $DraftInfo = null; - private ?array $pgpSigned = null; + public ?array $pgpSigned = null; private ?array $pgpEncrypted = null; public ?array $smimeSigned = null; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php index 61c847220..bf682ed55 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php @@ -7,6 +7,7 @@ use RainLoop\Exceptions\ClientException; use RainLoop\Model\Account; use RainLoop\Notifications; use MailSo\Imap\SequenceSet; +use MailSo\Imap\Enumerations\FetchType; use MailSo\Imap\Enumerations\MessageFlag; use MailSo\Mime\Part as MimePart; use MailSo\Mime\Enumerations\Header as MimeEnumHeader; @@ -455,6 +456,63 @@ trait Messages try { $oMessage = $this->MailClient()->Message($sFolder, $iUid, true, $this->Cacher($oAccount)); + + // S/MIME signed. Verify it, so we have the raw mime body to show + if ($oMessage->smimeSigned) try { + $bOpaque = !$oMessage->smimeSigned['detached']; + $sBody = $this->ImapClient()->FetchMessagePart( + $oMessage->Uid, + $oMessage->smimeSigned['partId'] + ); + $result = (new \SnappyMail\SMime\OpenSSL(''))->verify($sBody, null, $bOpaque); + if ($result) { + if ($bOpaque) { + $oMessage->smimeSigned['body'] = $result['body']; + } + $oMessage->smimeSigned['success'] = $result['success']; + } + } catch (\Throwable $e) { + $this->logException($e); + } + + if ($oMessage->pgpSigned) try { + $GPG = $this->GnuPG(); + if ($GPG) { + if ($oMessage->pgpSigned['sigPartId']) { + $sPartId = $oMessage->pgpSigned['partId']; + $sSigPartId = $oMessage->pgpSigned['sigPartId']; + $aParts = [ + FetchType::BODY_PEEK.'['.$sPartId.']', + // 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.'['.$sPartId.'.MIME]', + FetchType::BODY_PEEK.'['.$sSigPartId.']' + ]; + $oFetchResponse = $this->ImapClient()->Fetch($aParts, $oMessage->Uid, true)[0]; + $sBodyMime = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.'.MIME]'); + $info = $this->GnuPG()->verify( + \preg_replace('/\\r?\\n/su', "\r\n", + $sBodyMime . $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.']') + ), + \preg_replace('/[^\x00-\x7F]/', '', + $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']') + ) + ); + } else { + // clearsigned text + $info = $this->GnuPG()->verify($oMessage->sPlain, ''); + } + if (!empty($info[0]) && 0 == $info[0]['status']) { + $info = $info[0]; + $oMessage->pgpSigned = [ + 'fingerprint' => $info['fingerprint'], + 'success' => true + ]; + } + } + } catch (\Throwable $e) { + $this->logException($e); + } } catch (\Throwable $oException) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php index 2f2eae016..f70d9a021 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php @@ -329,7 +329,7 @@ trait Pgp 'text' => \preg_replace('/\\r?\\n/su', "\r\n", $sBodyMime . $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.']') ), - 'signature' => preg_replace('/[^\x00-\x7F]/', '', + 'signature' => \preg_replace('/[^\x00-\x7F]/', '', $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sSigPartId.']') ) ]; diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php index 72494fd68..bbea8bbb0 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php @@ -15,7 +15,7 @@ abstract class GnuPG return GPG::isSupported(); } - private static $instance; + private static $instance = null; public static function getInstance(string $homedir) : ?PGPInterface { if (!static::$instance) { diff --git a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html index 8459043bd..98c6fc383 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html @@ -300,10 +300,10 @@
-
+
-
+
@@ -315,7 +315,7 @@
-
+