mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-05 07:27:04 +03:00
Handle S/MIME encrypt, decrypt, sign and very everywhere #259
This commit is contained in:
parent
44a623543f
commit
5a2d2fd0e5
7 changed files with 85 additions and 48 deletions
|
|
@ -35,7 +35,7 @@ import { AccountUserStore } from 'Stores/User/Account';
|
||||||
import { ContactUserStore } from 'Stores/User/Contact';
|
import { ContactUserStore } from 'Stores/User/Contact';
|
||||||
import { FolderUserStore } from 'Stores/User/Folder';
|
import { FolderUserStore } from 'Stores/User/Folder';
|
||||||
import { PgpUserStore } from 'Stores/User/Pgp';
|
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 { MessagelistUserStore } from 'Stores/User/Messagelist';
|
||||||
import { ThemeStore, initThemes } from 'Stores/Theme';
|
import { ThemeStore, initThemes } from 'Stores/Theme';
|
||||||
import { LanguageStore } from 'Stores/Language';
|
import { LanguageStore } from 'Stores/Language';
|
||||||
|
|
@ -217,7 +217,8 @@ export class AppUser extends AbstractApp {
|
||||||
setInterval(reloadTime, 60000);
|
setInterval(reloadTime, 60000);
|
||||||
|
|
||||||
PgpUserStore.init();
|
PgpUserStore.init();
|
||||||
SMimeUserStore.loadCertificates();
|
// TODO:
|
||||||
|
// SMimeUserStore.loadCertificates();
|
||||||
|
|
||||||
setTimeout(() => mailToHelper(SettingsGet('mailToEmail')), 500);
|
setTimeout(() => mailToHelper(SettingsGet('mailToEmail')), 500);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import { EmailModel } from 'Model/Email';
|
||||||
*/
|
*/
|
||||||
export function MimeToMessage(data, message)
|
export function MimeToMessage(data, message)
|
||||||
{
|
{
|
||||||
let signed;
|
|
||||||
const struct = ParseMime(data);
|
const struct = ParseMime(data);
|
||||||
if (struct.headers) {
|
if (struct.headers) {
|
||||||
let html = struct.getByContentType('text/html'),
|
let html = struct.getByContentType('text/html'),
|
||||||
|
|
@ -65,12 +64,21 @@ export function MimeToMessage(data, message)
|
||||||
} else {
|
} else {
|
||||||
message.attachments.push(attachment);
|
message.attachments.push(attachment);
|
||||||
}
|
}
|
||||||
} else if ('multipart/signed' === type.value && 'application/pgp-signature' === type.params.protocol) {
|
} else if ('multipart/signed' === type.value) {
|
||||||
signed = {
|
let protocol = type.params.protocol;
|
||||||
micAlg: type.micalg,
|
if ('application/pgp-signature' === protocol) {
|
||||||
bodyPart: part.parts[0],
|
message.pgpSigned({
|
||||||
sigPart: part.parts[1]
|
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);
|
message.plain(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!signed && message.plain().includes(BEGIN_PGP_MESSAGE)) {
|
if (message.plain().includes(BEGIN_PGP_MESSAGE)) {
|
||||||
signed = true;
|
message.pgpSigned(true);
|
||||||
}
|
}
|
||||||
message.pgpSigned(signed);
|
|
||||||
|
|
||||||
// TODO: Verify instantly?
|
// TODO: Verify instantly?
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import { PgpUserStore } from 'Stores/User/Pgp';
|
||||||
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
|
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
|
||||||
import { GnuPGUserStore } from 'Stores/User/GnuPG';
|
import { GnuPGUserStore } from 'Stores/User/GnuPG';
|
||||||
//import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport';
|
//import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport';
|
||||||
|
import { SMimeUserStore } from 'Stores/User/SMime';
|
||||||
|
|
||||||
import { MessageUserStore } from 'Stores/User/Message';
|
import { MessageUserStore } from 'Stores/User/Message';
|
||||||
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
||||||
|
|
@ -377,21 +378,21 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
});
|
});
|
||||||
this.pgpSignKey(result)
|
this.pgpSignKey(result)
|
||||||
});
|
});
|
||||||
this.initPgpEncrypt();
|
this.initEncrypt();
|
||||||
},
|
},
|
||||||
|
|
||||||
cc: value => {
|
cc: value => {
|
||||||
if (false === this.showCc() && value.length) {
|
if (false === this.showCc() && value.length) {
|
||||||
this.showCc(true);
|
this.showCc(true);
|
||||||
}
|
}
|
||||||
this.initPgpEncrypt();
|
this.initEncrypt();
|
||||||
},
|
},
|
||||||
|
|
||||||
bcc: value => {
|
bcc: value => {
|
||||||
if (false === this.showBcc() && value.length) {
|
if (false === this.showBcc() && value.length) {
|
||||||
this.showBcc(true);
|
this.showBcc(true);
|
||||||
}
|
}
|
||||||
this.initPgpEncrypt();
|
this.initEncrypt();
|
||||||
},
|
},
|
||||||
|
|
||||||
replyTo: value => {
|
replyTo: value => {
|
||||||
|
|
@ -410,7 +411,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
if (this.emptyToError() && value.length) {
|
if (this.emptyToError() && value.length) {
|
||||||
this.emptyToError(false);
|
this.emptyToError(false);
|
||||||
}
|
}
|
||||||
this.initPgpEncrypt();
|
this.initEncrypt();
|
||||||
},
|
},
|
||||||
|
|
||||||
attachmentsInProcess: value => {
|
attachmentsInProcess: value => {
|
||||||
|
|
@ -1387,7 +1388,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
].join(',').split(',').map(value => getEmail(value.trim())).validUnique();
|
].join(',').split(',').map(value => getEmail(value.trim())).validUnique();
|
||||||
}
|
}
|
||||||
|
|
||||||
initPgpEncrypt() {
|
initEncrypt() {
|
||||||
const recipients = this.allRecipients();
|
const recipients = this.allRecipients();
|
||||||
PgpUserStore.hasPublicKeyForEmails(recipients).then(result => {
|
PgpUserStore.hasPublicKeyForEmails(recipients).then(result => {
|
||||||
console.log({canPgpEncrypt:result});
|
console.log({canPgpEncrypt:result});
|
||||||
|
|
@ -1401,6 +1402,14 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
// this.dropMailvelope();
|
// 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)
|
async getMessageRequestParams(sSaveFolder, draft)
|
||||||
|
|
@ -1561,12 +1570,13 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
// Does encrypt attachments
|
// Does encrypt attachments
|
||||||
params.encryptFingerprints = JSON.stringify(GnuPGUserStore.getPublicKeyFingerprints(recipients));
|
params.encryptFingerprints = JSON.stringify(GnuPGUserStore.getPublicKeyFingerprints(recipients));
|
||||||
autocrypt();
|
autocrypt();
|
||||||
/*
|
} else if (this.canSMimeEncrypt() && identity && identity.smimeKey() && identity.smimeCertificate()) {
|
||||||
} else if (identity && identity.smimeCertificate()) {
|
|
||||||
// TODO: S/MIME certificates of all recipients
|
|
||||||
params.encryptCertificates = [identity.smimeCertificate()];
|
params.encryptCertificates = [identity.smimeCertificate()];
|
||||||
}
|
SMimeUserStore.forEach(certificate => {
|
||||||
*/
|
certificate.emailAddress != identity.email()
|
||||||
|
&& recipients.includes(certificate.emailAddress)
|
||||||
|
&& params.encryptCertificates.push(certificate.id)
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw 'Encryption with ' + encrypt + ' not yet implemented';
|
throw 'Encryption with ' + encrypt + ' not yet implemented';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1194,6 +1194,17 @@ trait Messages
|
||||||
$oMessage->Attachments()->Clear();
|
$oMessage->Attachments()->Clear();
|
||||||
|
|
||||||
$SMIME = $this->SMIME();
|
$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);
|
$sEncrypted = $SMIME->encrypt($tmp, $aCertificates);
|
||||||
|
|
||||||
$oPart = new MimePart;
|
$oPart = new MimePart;
|
||||||
|
|
|
||||||
|
|
@ -114,34 +114,37 @@ trait SMime
|
||||||
|
|
||||||
public function DoSMimeVerifyMessage() : array
|
public function DoSMimeVerifyMessage() : array
|
||||||
{
|
{
|
||||||
$sFolderName = $this->GetActionParam('folder', '');
|
$sBody = $this->GetActionParam('bodyPart', '');
|
||||||
$iUid = (int) $this->GetActionParam('uid', 0);
|
|
||||||
$sPartId = $this->GetActionParam('partId', '');
|
|
||||||
$sMicAlg = $this->GetActionParam('micAlg', '');
|
|
||||||
$bDetached = !empty($this->GetActionParam('detached', 0));
|
$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();
|
$this->initMailClientConnection();
|
||||||
$oImapClient = $this->ImapClient();
|
$oImapClient = $this->ImapClient();
|
||||||
$oImapClient->FolderExamine($sFolderName);
|
$oImapClient->FolderExamine($sFolderName);
|
||||||
|
|
||||||
if ('TEXT' === $sPartId) {
|
if ('TEXT' === $sPartId) {
|
||||||
$oFetchResponse = $oImapClient->Fetch([
|
$oFetchResponse = $oImapClient->Fetch([
|
||||||
FetchType::BODY_PEEK.'['.$sPartId.']',
|
FetchType::BODY_PEEK.'['.$sPartId.']',
|
||||||
// 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_HEADER_PEEK
|
FetchType::BODY_HEADER_PEEK
|
||||||
], $iUid, true)[0];
|
], $iUid, true)[0];
|
||||||
$sBody = $oFetchResponse->GetFetchValue(FetchType::BODY_HEADER);
|
$sBody = $oFetchResponse->GetFetchValue(FetchType::BODY_HEADER);
|
||||||
} else {
|
} else {
|
||||||
$oFetchResponse = $oImapClient->Fetch([
|
$oFetchResponse = $oImapClient->Fetch([
|
||||||
FetchType::BODY_PEEK.'['.$sPartId.']',
|
FetchType::BODY_PEEK.'['.$sPartId.']',
|
||||||
// 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.'['.$sPartId.'.MIME]'
|
FetchType::BODY_PEEK.'['.$sPartId.'.MIME]'
|
||||||
], $iUid, true)[0];
|
], $iUid, true)[0];
|
||||||
$sBody = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$sPartId.'.MIME]');
|
$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);
|
$result = $this->SMIME()->verify($sBody, null, !$bDetached);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@ class OpenSSL
|
||||||
$this->homedir = $homedir;
|
$this->homedir = $homedir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCertificate(string $filename)/* : string*/
|
||||||
|
{
|
||||||
|
return \file_get_contents("{$this->homedir}/{$filename}");
|
||||||
|
}
|
||||||
|
|
||||||
public function certificates() : array
|
public function certificates() : array
|
||||||
{
|
{
|
||||||
$cacheFile = "{$this->homedir}/certificates.json";
|
$cacheFile = "{$this->homedir}/certificates.json";
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@
|
||||||
<div id="mailvelope-settings" style="height:40em" data-bind="visible: canMailvelope"></div>
|
<div id="mailvelope-settings" style="height:40em" data-bind="visible: canMailvelope"></div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<!-- TODO
|
||||||
<details>
|
<details>
|
||||||
<summary class="legend">S/MIME Certificates</summary>
|
<summary class="legend">S/MIME Certificates</summary>
|
||||||
<table class="table table-hover list-table">
|
<table class="table table-hover list-table">
|
||||||
|
|
@ -137,7 +138,6 @@
|
||||||
<span data-i18n="CRYPTO/VALID_UNTIL"></span>:
|
<span data-i18n="CRYPTO/VALID_UNTIL"></span>:
|
||||||
<time data-time-format="FULL" data-bind="time:validTo_time_t"></time>
|
<time data-time-format="FULL" data-bind="time:validTo_time_t"></time>
|
||||||
</td>
|
</td>
|
||||||
<!--
|
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-small btn-danger button-confirm-delete" data-bind="css: {'delete-access': askDelete()}, click: remove"
|
<a class="btn btn-small btn-danger button-confirm-delete" data-bind="css: {'delete-access': askDelete()}, click: remove"
|
||||||
data-i18n="GLOBAL/ARE_YOU_SURE"></a>
|
data-i18n="GLOBAL/ARE_YOU_SURE"></a>
|
||||||
|
|
@ -145,8 +145,8 @@
|
||||||
<td>
|
<td>
|
||||||
<span class="delete-key fontastic" data-bind="visible: !askDelete(), click: openForDeletion">🗑</span>
|
<span class="delete-key fontastic" data-bind="visible: !askDelete(), click: openForDeletion">🗑</span>
|
||||||
</td>
|
</td>
|
||||||
-->
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</details>
|
</details>
|
||||||
|
-->
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue