#89 improved sign and encrypt

This commit is contained in:
the-djmaze 2022-02-09 13:44:17 +01:00
parent e7b2ed9881
commit 8e515ed2af
2 changed files with 124 additions and 105 deletions

View file

@ -104,25 +104,24 @@ class MimePart {
constructor() { constructor() {
this.headers = {}; this.headers = {};
this.body = ''; this.body = '';
this.boundary = '';
this.children = []; this.children = [];
} }
toString() { toString() {
const hasSub = this.children.length, const hasSub = this.children.length,
boundary = 'part' + Jua.randomId(), boundary = this.boundary || (this.boundary = 'part' + Jua.randomId()),
headers = this.headers; headers = this.headers;
if (hasSub) { if (hasSub) {
headers['Content-Type'] = headers['Content-Type'] + `; boundary="${boundary}"`; headers['Content-Type'] += `; boundary="${boundary}"`;
}
let result = Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join('\r\n') + '\r\n';
if (this.body) {
result += '\r\n' + this.body.replace(/\r?\n/g, '\r\n');
} }
let result =
Object.entries(headers).map(([key, value]) => `${key}: ${value}`).join('\r\n')
+ '\r\n\r\n'
+ this.body;
if (hasSub) { if (hasSub) {
this.children.forEach(part => { this.children.forEach(part => result += '\r\n--' + boundary + '\r\n' + part);
result = result + '\r\n--' + boundary + '\r\n' + part result += '\r\n--' + boundary + '--\r\n';
});
result = result + '\r\n--' + boundary + '--\r\n';
} }
return result; return result;
} }
@ -367,7 +366,7 @@ class ComposePopupView extends AbstractViewPopup {
async getMessageRequestParams(sSaveFolder, draft) async getMessageRequestParams(sSaveFolder, draft)
{ {
let const
identity = this.currentIdentity(), identity = this.currentIdentity(),
params = { params = {
IdentityID: identity.id(), IdentityID: identity.id(),
@ -391,8 +390,9 @@ class ComposePopupView extends AbstractViewPopup {
recipients = draft ? [identity.email()] : this.allRecipients(), recipients = draft ? [identity.email()] : this.allRecipients(),
sign = !draft && this.pgpSign() && this.canPgpSign(), sign = !draft && this.pgpSign() && this.canPgpSign(),
encrypt = this.pgpEncrypt() && this.canPgpEncrypt(), encrypt = this.pgpEncrypt() && this.canPgpEncrypt(),
TextIsHtml = this.oEditor.isHtml(), TextIsHtml = this.oEditor.isHtml();
Text = this.oEditor.getData();
let Text = this.oEditor.getData();
if (TextIsHtml) { if (TextIsHtml) {
let l; let l;
do { do {
@ -403,53 +403,70 @@ class ComposePopupView extends AbstractViewPopup {
// Remove hubspot data-hs- attributes // Remove hubspot data-hs- attributes
.replace(/(<[^>]+)\s+data-hs-[a-z-]+=("[^"]+"|'[^']+')/gi, '$1'); .replace(/(<[^>]+)\s+data-hs-[a-z-]+=("[^"]+"|'[^']+')/gi, '$1');
} while (l != Text.length) } while (l != Text.length)
params.Html = Text;
} else {
params.Text = Text;
} }
if (this.mailvelope && 'mailvelope' === this.viewArea()) { if (this.mailvelope && 'mailvelope' === this.viewArea()) {
params.Encrypted = draft params.Encrypted = draft
? await this.mailvelope.createDraft() ? await this.mailvelope.createDraft()
: await this.mailvelope.encrypt(recipients); : await this.mailvelope.encrypt(recipients);
} else if ('openpgp' == encrypt || (sign && 'openpgp' == sign[0])) { } else if (sign || encrypt) {
let data = new MimePart; let data = new MimePart;
data.headers['Content-Type'] = 'text/'+(TextIsHtml?'html':'plain')+'; charset="utf-8"'; data.headers['Content-Type'] = 'text/'+(TextIsHtml?'html':'plain')+'; charset="utf-8"';
data.headers['Content-Transfer-Encoding'] = 'base64'; data.headers['Content-Transfer-Encoding'] = 'base64';
data.body = base64_encode(Text); data.body = base64_encode(Text);
if (sign && sign[1]) { if (TextIsHtml) {
let signed = new MimePart; let alternative = new MimePart;
signed.headers['Content-Type'] = alternative.headers['Content-Type'] = 'multipart/alternative';
'multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"'; alternative.children.push(data);
signed.headers['Content-Transfer-Encoding'] = '7Bit'; data = new MimePart;
signed.children.push(data); data.headers['Content-Type'] = 'text/plain; charset="utf-8"';
let signature = new MimePart; data.headers['Content-Transfer-Encoding'] = 'base64';
signature.headers['Content-Type'] = 'application/pgp-signature; name="signature.asc"'; data.body = base64_encode(htmlToPlain(Text));
signature.headers['Content-Transfer-Encoding'] = '7Bit'; alternative.children.push(data);
signature.body = await OpenPGPUserStore.sign(data.toString(), sign[1], 1); data = alternative;
signed.children.push(signature);
data = signed;
}
if (encrypt) {
params.Encrypted = await OpenPGPUserStore.encrypt(data.toString(), recipients);
} else {
params.Signed = data.toString();
}
} else if ('gnupg' == encrypt || (sign && 'gnupg' == sign[0])) {
params.Html = TextIsHtml ? Text : '';
params.Text = TextIsHtml ? '' : Text;
/* // TODO: sign in PHP fails
if (sign) {
params.SignFingerprint = sign[1].fingerprint;
params.SignPassphrase = await GnuPGUserStore.sign(sign[1]);
} }
if (sign && !draft && sign[1]) {
if ('openpgp' == sign[0]) {
// Doesn't sign attachments
let signed = new MimePart;
signed.headers['Content-Type'] =
'multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"';
signed.headers['Content-Transfer-Encoding'] = '7Bit';
signed.children.push(data);
let signature = new MimePart;
signature.headers['Content-Type'] = 'application/pgp-signature; name="signature.asc"';
signature.headers['Content-Transfer-Encoding'] = '7Bit';
signature.body = await OpenPGPUserStore.sign(data.toString(), sign[1], 1);
signed.children.push(signature);
data = signed;
/*
} else if ('gnupg' == sign[0])) {
// TODO: sign in PHP fails
params.SignFingerprint = sign[1].fingerprint;
params.SignPassphrase = await GnuPGUserStore.sign(sign[1]);
*/ */
if (encrypt) { } else {
params.EncryptFingerprints = GnuPGUserStore.getPublicKeyFingerprints(recipients).join(','); throw 'Signing with ' + sign[0] + ' not yet implemented';
}
params.Signed = data.toString();
params.Boundary = data.boundary;
params.Html = params.Text = '';
}
if (encrypt) {
if ('openpgp' == encrypt) {
// Doesn't encrypt attachments
params.Encrypted = await OpenPGPUserStore.encrypt(data.toString(), recipients);
params.Signed = '';
} else if ('gnupg' == encrypt) {
// Does encrypt attachments
params.EncryptFingerprints = JSON.stringify(GnuPGUserStore.getPublicKeyFingerprints(recipients));
} else {
throw 'Encryption with ' + encrypt + ' not yet implemented';
}
} }
} else if (encrypt) {
throw 'Encryption with ' + encrypt + ' not yet implemented';
} else if (sign) {
throw 'Signing with ' + sign[0] + ' not yet implemented';
} else {
params.Html = TextIsHtml ? Text : '';
params.Text = TextIsHtml ? '' : Text;
} }
return params; return params;
} }

View file

@ -1102,43 +1102,10 @@ trait Messages
$aFoundContentLocationUrls = array(); $aFoundContentLocationUrls = array();
$oPart; $oPart;
if ($sHtml = $this->GetActionParam('Html', '')) { if ($sSigned = $this->GetActionParam('Signed', '')) {
$oPart = new MimePart;
$oPart->Headers->AddByName(
\MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
\MailSo\Mime\Enumerations\MimeType::MULTIPART_ALTERNATIVE
);
$oMessage->SubParts->append($oPart);
$sHtml = \MailSo\Base\HtmlUtils::BuildHtml($sHtml, $aFoundCids, $aFoundDataURL, $aFoundContentLocationUrls);
$this->Plugins()->RunHook('filter.message-html', array($oAccount, $oMessage, &$sHtml));
$oAlternativePart = new MimePart;
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/html; charset=utf-8');
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable');
$oAlternativePart->Body = \MailSo\Base\StreamWrappers\Binary::CreateStream(
\MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sHtml))),
'convert.quoted-printable-encode'
);
$oPart->SubParts->append($oAlternativePart);
$sPlain = \MailSo\Base\HtmlUtils::ConvertHtmlToPlain($sHtml);
$this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sPlain));
$oAlternativePart = new MimePart;
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain; charset=utf-8');
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable');
$oAlternativePart->Body = \MailSo\Base\StreamWrappers\Binary::CreateStream(
\MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sPlain))),
'convert.quoted-printable-encode'
);
$oPart->SubParts->append($oAlternativePart);
unset($oAlternativePart);
unset($sHtml);
unset($sPlain);
} else if ($sSigned = $this->GetActionParam('Signed', '')) {
$aSigned = \explode("\r\n\r\n", $sSigned, 2); $aSigned = \explode("\r\n\r\n", $sSigned, 2);
$sBoundary = \preg_replace('/^.+boundary="([^"]+)".+$/Dsi', '$1', $aSigned[0]); // $sBoundary = \preg_replace('/^.+boundary="([^"]+)".+$/Dsi', '$1', $aSigned[0]);
$sBoundary = $this->GetActionParam('Boundary', '');
$oPart = new MimePart; $oPart = new MimePart;
$oPart->Headers->AddByName( $oPart->Headers->AddByName(
@ -1178,40 +1145,76 @@ trait Messages
unset($sEncrypted); unset($sEncrypted);
} else { } else {
$sPlain = $this->GetActionParam('Text', ''); if ($sHtml = $this->GetActionParam('Html', '')) {
if ($sSignature = $this->GetActionParam('Signature', null)) {
$oPart = new MimePart; $oPart = new MimePart;
$oPart->Headers->AddByName( $oPart->Headers->AddByName(
\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, \MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
'multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"' \MailSo\Mime\Enumerations\MimeType::MULTIPART_ALTERNATIVE
); );
$oMessage->SubParts->append($oPart); $oMessage->SubParts->append($oPart);
$sHtml = \MailSo\Base\HtmlUtils::BuildHtml($sHtml, $aFoundCids, $aFoundDataURL, $aFoundContentLocationUrls);
$this->Plugins()->RunHook('filter.message-html', array($oAccount, $oMessage, &$sHtml));
$oAlternativePart = new MimePart; $oAlternativePart = new MimePart;
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain; charset="utf-8"; protected-headers="v1"'); $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/html; charset=utf-8');
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'base64'); $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable');
$oAlternativePart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sPlain))); $oAlternativePart->Body = \MailSo\Base\StreamWrappers\Binary::CreateStream(
\MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sHtml))),
'convert.quoted-printable-encode'
);
$oPart->SubParts->append($oAlternativePart); $oPart->SubParts->append($oAlternativePart);
$oAlternativePart = new MimePart; $sPlain = \MailSo\Base\HtmlUtils::ConvertHtmlToPlain($sHtml);
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'application/pgp-signature; name="signature.asc"');
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, '7Bit');
$oAlternativePart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sSignature)));
$oPart->SubParts->append($oAlternativePart);
} else {
$this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sPlain)); $this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sPlain));
$oAlternativePart = new MimePart; $oAlternativePart = new MimePart;
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain; charset="utf-8"'); $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain; charset=utf-8');
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable'); $oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable');
$oAlternativePart->Body = \MailSo\Base\StreamWrappers\Binary::CreateStream( $oAlternativePart->Body = \MailSo\Base\StreamWrappers\Binary::CreateStream(
\MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sPlain))), \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sPlain))),
'convert.quoted-printable-encode' 'convert.quoted-printable-encode'
); );
$oMessage->SubParts->append($oAlternativePart); $oPart->SubParts->append($oAlternativePart);
unset($oAlternativePart);
unset($sHtml);
unset($sPlain);
} else {
$sPlain = $this->GetActionParam('Text', '');
if ($sSignature = $this->GetActionParam('Signature', null)) {
$oPart = new MimePart;
$oPart->Headers->AddByName(
\MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
'multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"'
);
$oMessage->SubParts->append($oPart);
$oAlternativePart = new MimePart;
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain; charset="utf-8"; protected-headers="v1"');
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'base64');
$oAlternativePart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sPlain)));
$oPart->SubParts->append($oAlternativePart);
$oAlternativePart = new MimePart;
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'application/pgp-signature; name="signature.asc"');
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, '7Bit');
$oAlternativePart->Body = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sSignature)));
$oPart->SubParts->append($oAlternativePart);
} else {
$this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sPlain));
$oAlternativePart = new MimePart;
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'text/plain; charset="utf-8"');
$oAlternativePart->Headers->AddByName(\MailSo\Mime\Enumerations\Header::CONTENT_TRANSFER_ENCODING, 'quoted-printable');
$oAlternativePart->Body = \MailSo\Base\StreamWrappers\Binary::CreateStream(
\MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString(\preg_replace('/\\R/', "\r\n", \trim($sPlain))),
'convert.quoted-printable-encode'
);
$oMessage->SubParts->append($oAlternativePart);
}
unset($oAlternativePart);
unset($sSignature);
unset($sPlain);
} }
unset($oAlternativePart);
unset($sSignature);
unset($sPlain);
} }
unset($oPart); unset($oPart);
@ -1305,15 +1308,14 @@ trait Messages
*/ */
} }
// TODO: encrypt $aFingerprints = \json_decode($this->GetActionParam('EncryptFingerprints', ''), true);
$sFingerprints = $this->GetActionParam('EncryptFingerprints', ''); if ($aFingerprints) {
if ($sFingerprints) {
$GPG = $this->GnuPG(); $GPG = $this->GnuPG();
$oBody = $oMessage->GetRootPart(); $oBody = $oMessage->GetRootPart();
$fp = \fopen('php://memory', 'r+b'); $fp = \fopen('php://memory', 'r+b');
$resource = $oBody->ToStream(); $resource = $oBody->ToStream();
\stream_copy_to_stream($resource, $fp); \stream_copy_to_stream($resource, $fp);
foreach (\explode(',', $sFingerprints) as $sFingerprint) { foreach ($aFingerprints as $sFingerprint) {
$GPG->addEncryptKey($sFingerprint); $GPG->addEncryptKey($sFingerprint);
} }
$sEncrypted = $GPG->encryptStream($fp); $sEncrypted = $GPG->encryptStream($fp);