diff --git a/dev/App/User.js b/dev/App/User.js index 21d7b588b..ac473088a 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -35,7 +35,7 @@ import { AccountUserStore } from 'Stores/User/Account'; import { ContactUserStore } from 'Stores/User/Contact'; import { FolderUserStore } from 'Stores/User/Folder'; import { PgpUserStore } from 'Stores/User/Pgp'; -import { SMimeUserStore } from 'Stores/User/SMime'; +//import { SMimeUserStore } from 'Stores/User/SMime'; import { MessagelistUserStore } from 'Stores/User/Messagelist'; import { ThemeStore, initThemes } from 'Stores/Theme'; import { LanguageStore } from 'Stores/Language'; @@ -217,7 +217,8 @@ export class AppUser extends AbstractApp { setInterval(reloadTime, 60000); PgpUserStore.init(); - SMimeUserStore.loadCertificates(); +// TODO: +// SMimeUserStore.loadCertificates(); setTimeout(() => mailToHelper(SettingsGet('mailToEmail')), 500); diff --git a/dev/Mime/Utils.js b/dev/Mime/Utils.js index 51a18b6b9..7ee9170e3 100644 --- a/dev/Mime/Utils.js +++ b/dev/Mime/Utils.js @@ -12,7 +12,6 @@ import { EmailModel } from 'Model/Email'; */ export function MimeToMessage(data, message) { - let signed; const struct = ParseMime(data); if (struct.headers) { let html = struct.getByContentType('text/html'), @@ -65,12 +64,21 @@ export function MimeToMessage(data, message) } else { message.attachments.push(attachment); } - } else if ('multipart/signed' === type.value && 'application/pgp-signature' === type.params.protocol) { - signed = { - micAlg: type.micalg, - bodyPart: part.parts[0], - sigPart: part.parts[1] - }; + } else if ('multipart/signed' === type.value) { + let protocol = type.params.protocol; + if ('application/pgp-signature' === protocol) { + message.pgpSigned({ + micAlg: type.micalg, + bodyPart: part.parts[0], + sigPart: part.parts[1] + }); + } else if ('application/pkcs7-signature' === protocol.replace('x-')) { + message.smimeSigned({ + micAlg: type.micalg, + bodyPart: part, + detached: true + }); + } } }); @@ -81,10 +89,9 @@ export function MimeToMessage(data, message) message.plain(data); } - if (!signed && message.plain().includes(BEGIN_PGP_MESSAGE)) { - signed = true; + if (message.plain().includes(BEGIN_PGP_MESSAGE)) { + message.pgpSigned(true); } - message.pgpSigned(signed); // TODO: Verify instantly? } diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 69530f487..95ecb4833 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -33,6 +33,7 @@ import { PgpUserStore } from 'Stores/User/Pgp'; import { OpenPGPUserStore } from 'Stores/User/OpenPGP'; import { GnuPGUserStore } from 'Stores/User/GnuPG'; //import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport'; +import { SMimeUserStore } from 'Stores/User/SMime'; import { MessageUserStore } from 'Stores/User/Message'; import { MessagelistUserStore } from 'Stores/User/Messagelist'; @@ -377,21 +378,21 @@ export class ComposePopupView extends AbstractViewPopup { }); this.pgpSignKey(result) }); - this.initPgpEncrypt(); + this.initEncrypt(); }, cc: value => { if (false === this.showCc() && value.length) { this.showCc(true); } - this.initPgpEncrypt(); + this.initEncrypt(); }, bcc: value => { if (false === this.showBcc() && value.length) { this.showBcc(true); } - this.initPgpEncrypt(); + this.initEncrypt(); }, replyTo: value => { @@ -410,7 +411,7 @@ export class ComposePopupView extends AbstractViewPopup { if (this.emptyToError() && value.length) { this.emptyToError(false); } - this.initPgpEncrypt(); + this.initEncrypt(); }, attachmentsInProcess: value => { @@ -1387,7 +1388,7 @@ export class ComposePopupView extends AbstractViewPopup { ].join(',').split(',').map(value => getEmail(value.trim())).validUnique(); } - initPgpEncrypt() { + initEncrypt() { const recipients = this.allRecipients(); PgpUserStore.hasPublicKeyForEmails(recipients).then(result => { console.log({canPgpEncrypt:result}); @@ -1401,6 +1402,14 @@ export class ComposePopupView extends AbstractViewPopup { // this.dropMailvelope(); } }); + const count = recipients.length, + identity = this.currentIdentity(), + from = (identity.smimeKey() && identity.smimeCertificate()) ? identity.email() : null, + length = count ? recipients.filter(email => + email == from + || SMimeUserStore.find(certificate => email == certificate.emailAddress && certificate.smimeencrypt) + ).length : 0; + this.canSMimeEncrypt(length && length === count); } async getMessageRequestParams(sSaveFolder, draft) @@ -1561,12 +1570,13 @@ export class ComposePopupView extends AbstractViewPopup { // Does encrypt attachments params.encryptFingerprints = JSON.stringify(GnuPGUserStore.getPublicKeyFingerprints(recipients)); autocrypt(); -/* - } else if (identity && identity.smimeCertificate()) { - // TODO: S/MIME certificates of all recipients + } else if (this.canSMimeEncrypt() && identity && identity.smimeKey() && identity.smimeCertificate()) { params.encryptCertificates = [identity.smimeCertificate()]; - } -*/ + SMimeUserStore.forEach(certificate => { + certificate.emailAddress != identity.email() + && recipients.includes(certificate.emailAddress) + && params.encryptCertificates.push(certificate.id) + }); } else { throw 'Encryption with ' + encrypt + ' not yet implemented'; } 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 70fa9da84..9e2db480d 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 @@ -1194,6 +1194,17 @@ trait Messages $oMessage->Attachments()->Clear(); $SMIME = $this->SMIME(); + $certificates = $SMIME->certificates(); + // Load certificates by id + foreach ($aCertificates as &$sCertificate) { + if (!\str_contains($sCertificate, '-----BEGIN CERTIFICATE-----')) { + foreach ($certificates as $certificate) { + if ($certificate['id'] === $sCertificate) { + $sCertificate = $SMIME->getCertificate($certificate['file']); + } + } + } + } $sEncrypted = $SMIME->encrypt($tmp, $aCertificates); $oPart = new MimePart; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php index 7f29c173e..da525db42 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php @@ -114,34 +114,37 @@ trait SMime public function DoSMimeVerifyMessage() : array { - $sFolderName = $this->GetActionParam('folder', ''); - $iUid = (int) $this->GetActionParam('uid', 0); - $sPartId = $this->GetActionParam('partId', ''); - $sMicAlg = $this->GetActionParam('micAlg', ''); + $sBody = $this->GetActionParam('bodyPart', ''); $bDetached = !empty($this->GetActionParam('detached', 0)); + if (!$sBody) { + $sFolderName = $this->GetActionParam('folder', ''); + $iUid = (int) $this->GetActionParam('uid', 0); + $sPartId = $this->GetActionParam('partId', ''); + $sMicAlg = $this->GetActionParam('micAlg', ''); - $this->initMailClientConnection(); - $oImapClient = $this->ImapClient(); - $oImapClient->FolderExamine($sFolderName); + $this->initMailClientConnection(); + $oImapClient = $this->ImapClient(); + $oImapClient->FolderExamine($sFolderName); - if ('TEXT' === $sPartId) { - $oFetchResponse = $oImapClient->Fetch([ - 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_HEADER_PEEK - ], $iUid, true)[0]; - $sBody = $oFetchResponse->GetFetchValue(FetchType::BODY_HEADER); - } else { - $oFetchResponse = $oImapClient->Fetch([ - 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]' - ], $iUid, true)[0]; - $sBody = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.'.MIME]'); + if ('TEXT' === $sPartId) { + $oFetchResponse = $oImapClient->Fetch([ + 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_HEADER_PEEK + ], $iUid, true)[0]; + $sBody = $oFetchResponse->GetFetchValue(FetchType::BODY_HEADER); + } else { + $oFetchResponse = $oImapClient->Fetch([ + 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]' + ], $iUid, true)[0]; + $sBody = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.'.MIME]'); + } + $sBody .= $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.']'); } - $sBody .= $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.']'); $result = $this->SMIME()->verify($sBody, null, !$bDetached); diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/smime/openssl.php b/snappymail/v/0.0.0/app/libraries/snappymail/smime/openssl.php index 92740f0b4..2d7ed6a74 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/smime/openssl.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/smime/openssl.php @@ -25,6 +25,11 @@ class OpenSSL $this->homedir = $homedir; } + public function getCertificate(string $filename)/* : string*/ + { + return \file_get_contents("{$this->homedir}/{$filename}"); + } + public function certificates() : array { $cacheFile = "{$this->homedir}/certificates.json"; diff --git a/snappymail/v/0.0.0/app/templates/Views/User/SettingsSecurity.html b/snappymail/v/0.0.0/app/templates/Views/User/SettingsSecurity.html index c7ceb3464..5df2dd528 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/SettingsSecurity.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/SettingsSecurity.html @@ -123,6 +123,7 @@
+ +-->