diff options
author | Patrick Brunschwig <[email protected]> | 2019-06-15 21:06:40 +0200 |
---|---|---|
committer | Patrick Brunschwig <[email protected]> | 2019-06-15 21:06:40 +0200 |
commit | 6b3e1ea554a563531c0d43dff68ee19274d077ce (patch) | |
tree | 0950c3331143192ec5805d7725768dae680d0106 | |
parent | 74648654b0fede31f59e79a01df19917d6c16e3b (diff) | |
download | enigmail-filter-v2.tar.gz enigmail-filter-v2.tar.bz2 enigmail-filter-v2.zip |
completed re-implementation of new persistent decryption/encryptionfilter-v2
-rw-r--r-- | package/persistentCrypto.jsm | 199 | ||||
-rw-r--r-- | package/tests/persistentCrypto-test.js | 95 | ||||
-rw-r--r-- | package/tests/resources/encrypted-pgpmime-email.eml | 44 |
3 files changed, 274 insertions, 64 deletions
diff --git a/package/persistentCrypto.jsm b/package/persistentCrypto.jsm index ebcfcdd7..1e5cd8c2 100644 --- a/package/persistentCrypto.jsm +++ b/package/persistentCrypto.jsm @@ -30,6 +30,7 @@ const EnigmailEncryption = ChromeUtils.import("chrome://enigmail/content/modules const getFixExchangeMsg = EnigmailLazy.loader("enigmail/fixExchangeMsg.jsm", "EnigmailFixExchangeMsg"); const getDecryption = EnigmailLazy.loader("enigmail/decryption.jsm", "EnigmailDecryption"); const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog"); +const getGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent"); const STATUS_OK = 0; const STATUS_FAILURE = 1; @@ -163,6 +164,11 @@ CryptMessageIntoFolder.prototype = { messageParseCallback: async function(mimeTree, msgHdr) { this.mimeTree = mimeTree; this.hdr = msgHdr; + + if (mimeTree.headers.has("subject")) { + this.subject = mimeTree.headers.get("subject"); + } + await this.decryptMimeTree(mimeTree); let msg = ""; @@ -259,39 +265,6 @@ CryptMessageIntoFolder.prototype = { return msg; }, - readAttachment: function(attachment, strippedName) { - return new Promise( - function(resolve, reject) { - EnigmailLog.DEBUG("persistentCrypto.jsm: readAttachment\n"); - let o; - var f = function _cb(data) { - EnigmailLog.DEBUG("persistentCrypto.jsm: readAttachment - got data (" + data.length + ")\n"); - o = { - type: "attachment", - data: data, - name: strippedName ? strippedName : attachment.name, - partNum: attachment.partNum, - origName: attachment.name, - status: STATUS_NOT_REQUIRED - }; - resolve(o); - }; - - try { - var bufferListener = EnigmailStreams.newStringStreamListener(f); - var ioServ = Cc[IOSERVICE_CONTRACTID].getService(Components.interfaces.nsIIOService); - var msgUri = ioServ.newURI(attachment.url, null, null); - - var channel = EnigmailStreams.createChannel(msgUri); - channel.asyncOpen(bufferListener, msgUri); - } catch (ex) { - reject(o); - } - } - ); - }, - - /** * Walk through the MIME message structure and decrypt the body if there is something to decrypt */ @@ -304,6 +277,8 @@ CryptMessageIntoFolder.prototype = { if (this.isPgpMime(mimePart)) { this.decryptPGPMIME(mimePart); + } else if (getAttachmentName(mimePart)) { + this.decryptAttachment(mimePart); } else { this.decryptINLINE(mimePart); } @@ -412,7 +387,6 @@ CryptMessageIntoFolder.prototype = { if (data.substr(bodyIndex).search(/\r?\n$/) === 0) { return; - } let m = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders); @@ -461,6 +435,101 @@ CryptMessageIntoFolder.prototype = { this.messageDecrypted = true; }, + decryptAttachment: function(mimePart) { + + EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment()\n"); + let attachmentHead = mimePart.body.substr(0, 30); + if (attachmentHead.search(/-----BEGIN PGP \w{5,10} KEY BLOCK-----/) >= 0) { + // attachment appears to be a PGP key file, we just go-a-head + return; + } + + let attachmentName = getAttachmentName(mimePart); + if (attachmentName.search(/\.(pgp|asc|gpg)$/) < 0 && + mimePart.body.search(/^-----BEGIN PGP ENCRYPTED MESSAGE-----$/m) < 0) { + return; + } + + attachmentName = attachmentName.replace(/\.(pgp|asc|gpg)$/, ""); + + var enigmailSvc = EnigmailCore.getService(); + var args = EnigmailGpg.getStandardArgs(true); + args.push("-d"); + + var statusMsgObj = {}; + var cmdLineObj = {}; + var exitCode = -1; + var statusFlagsObj = {}; + var errorMsgObj = {}; + statusFlagsObj.value = 0; + + var listener = EnigmailExecution.newSimpleListener( + function _stdin(pipe) { + pipe.write(mimePart.body); + pipe.close(); + + } + ); + + do { + var proc = EnigmailExecution.execStart(getGpgAgent().agentPath, args, false, null, listener, statusFlagsObj); + if (!proc) { + return; + } + // Wait for child STDOUT to close + proc.wait(); + EnigmailExecution.execEnd(listener, statusFlagsObj, statusMsgObj, cmdLineObj, errorMsgObj); + + if ((listener.stdoutData && listener.stdoutData.length > 0) || + (statusFlagsObj.value & EnigmailConstants.DECRYPTION_OKAY)) { + EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption OK\n"); + exitCode = 0; + } else if (statusFlagsObj.value & (EnigmailConstants.DECRYPTION_FAILED | EnigmailConstants.MISSING_MDC)) { + EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption without MDC protection\n"); + exitCode = 0; + } else if (statusFlagsObj.value & EnigmailConstants.DECRYPTION_FAILED) { + EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption failed\n"); + // since we cannot find out if the user wants to cancel + // we should ask + let msg = EnigmailLocale.getString("converter.decryptAtt.failed", [attachmentName, this.subject]); + + if (!getDialog().confirmDlg(null, msg, + EnigmailLocale.getString("dlg.button.retry"), EnigmailLocale.getString("dlg.button.skip"))) { + return; + } + } else if (statusFlagsObj.value & EnigmailConstants.DECRYPTION_INCOMPLETE) { + // failure; message not complete + EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption incomplete\n"); + return; + } else { + // there is nothing to be decrypted + EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: no decryption required\n"); + return; + } + + } while (exitCode !== 0); + + + EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decrypted to " + listener.stdoutData.length + " bytes\n"); + this.decryptedMessage = true; + mimePart.body = listener.stdoutData; + mimePart.headers._rawHeaders.set("content-disposition", `attachment; filename="${attachmentName}"`); + mimePart.headers._rawHeaders.set("content-transfer-encoding", ["base64"]); + let origCt = mimePart.headers.get("content-type"); + let ct = origCt.type; + + + for (let i of origCt.entries()) { + if (i[0].toLowerCase() === "name") { + i[1] = i[1].replace(/\.(pgp|asc|gpg)$/, ""); + } + ct += `; ${i[0]}="${i[1]}"`; + } + + mimePart.headers._rawHeaders.set("content-type", [ct]); + }, + + decryptINLINE: function(mimePart) { EnigmailLog.DEBUG("persistentCrypto.jsm: decryptINLINE()\n"); @@ -593,11 +662,10 @@ CryptMessageIntoFolder.prototype = { let j = decryptedMessage.search(/[^\x01-\x7F]/); // eslint-disable-line no-control-regex if (j >= 0) { mimePart.headers._rawHeaders.set('content-transfer-encoding', ['base64']); - mimePart.body = EnigmailData.encodeBase64(decryptedMessage); } else { - mimePart.body = decryptedMessage; mimePart.headers._rawHeaders.set('content-transfer-encoding', ['8bit']); } + mimePart.body = decryptedMessage; let origCharset = getCharset(getHeaderValue(mimePart, 'content-type')); if (origCharset) { @@ -663,7 +731,20 @@ CryptMessageIntoFolder.prototype = { } if (mimePart.body.length > 0) { - msg += mimePart.body; + let encoding = getTransferEncoding(mimePart); + if (!encoding) encoding = "8bit"; + + if (encoding === "quoted-printable") { + mimePart.headers._rawHeaders.set("content-transfer-encoding", ["base64"]); + encoding = "base64"; + } + + if (encoding === "base64") { + msg += EnigmailData.encodeBase64(mimePart.body); + } else { + msg += mimePart.body; + } + } if (mimePart.subParts.length > 0) { @@ -672,10 +753,9 @@ CryptMessageIntoFolder.prototype = { for (let i in mimePart.subParts) { msg += `--${boundary}\r\n`; msg += this.mimeToString(mimePart.subParts[i], true); - } - - if (msg.search(/[\r\n]$/) < 0) { - msg += "\r\n"; + if (msg.search(/[\r\n]$/) < 0) { + msg += "\r\n"; + } } msg += `--${boundary}--\r\n`; @@ -889,7 +969,7 @@ function getRfc822Headers(headerArr, contentType, ignoreHeadersArr) { function getContentType(mime) { try { - if (mime.headers.has("content-type")) { + if (mime && ("headers" in mime) && mime.headers.has("content-type")) { return mime.headers.get("content-type").type.toLowerCase(); } } catch (e) { @@ -901,7 +981,7 @@ function getContentType(mime) { // return the content of the boundary parameter function getBoundary(mime) { try { - if (mime.headers.has("content-type")) { + if (mime && ("headers" in mime) && mime.headers.has("content-type")) { return mime.headers.get("content-type").get("boundary"); } } catch (e) { @@ -912,7 +992,7 @@ function getBoundary(mime) { function getCharset(mime) { try { - if (mime.headers.has("content-type")) { + if (mime && ("headers" in mime) && mime.headers.has("content-type")) { let c = mime.headers.get("content-type").get("charset"); if (c) return c.toLowerCase(); } @@ -924,7 +1004,7 @@ function getCharset(mime) { function getProtocol(mime) { try { - if (mime.headers.has("content-type")) { + if (mime && ("headers" in mime) && mime.headers.has("content-type")) { let c = mime.headers.get("content-type").get("protocol"); if (c) return c.toLowerCase(); } @@ -934,6 +1014,35 @@ function getProtocol(mime) { return ""; } +function getTransferEncoding(mime) { + try { + if (mime && ("headers" in mime) && mime.headers._rawHeaders.has("content-transfer-encoding")) { + let c = mime.headers._rawHeaders.get("content-transfer-encoding")[0]; + if (c) return c.toLowerCase(); + } + } catch (e) { + EnigmailLog.DEBUG("persistentCrypto.jsm: getTransferEncoding: " + e + "\n"); + } + return "8Bit"; +} + + +function getAttachmentName(mime) { + try { + if (mime && ("headers" in mime) && mime.headers.has("content-disposition")) { + let c = mime.headers.get("content-disposition")[0]; + if (c) { + if (c.search(/^attachment/i) === 0) { + return EnigmailMime.getParameter(c, "filename"); + } + } + } + } catch (e) { + EnigmailLog.DEBUG("persistentCrypto.jsm: getAttachmentName: " + e + "\n"); + } + return null; +} + function getPepSubject(mimeString) { EnigmailLog.DEBUG("persistentCrypto.jsm: getPepSubject()\n"); diff --git a/package/tests/persistentCrypto-test.js b/package/tests/persistentCrypto-test.js index 454c772a..f264d356 100644 --- a/package/tests/persistentCrypto-test.js +++ b/package/tests/persistentCrypto-test.js @@ -12,7 +12,7 @@ do_load_module("file://" + do_get_cwd().path + "/testHelper.js"); /*global TestHelper: false, component: false, withTestGpgHome: false, withEnigmail: false */ TestHelper.loadDirectly("tests/mailHelper.js"); /*global MailHelper: false */ -testing("persistentCrypto.jsm"); /*global EnigmailPersistentCrypto: false, Promise: false */ +testing("persistentCrypto.jsm"); /*global EnigmailPersistentCrypto: false, Promise: false, EnigmailMime: false */ var EnigmailKeyRing = component("enigmail/keyRing.jsm").EnigmailKeyRing; /*global MsgHdrToMimeMessage: false, MimeMessage: false, MimeContainer: false, EnigmailStreams: false */ @@ -70,7 +70,7 @@ test(withTestGpgHome(withEnigmail(function messageIsMovedAndDecrypted() { loadSecretKey(); MailHelper.cleanMailFolder(MailHelper.rootFolder); const sourceFolder = MailHelper.createMailFolder("source-box"); - MailHelper.loadEmailToMailFolder("resources/encrypted-email.eml", sourceFolder); + MailHelper.loadEmailToMailFolder("resources/encrypted-pgpmime-email.eml", sourceFolder); const header = MailHelper.fetchFirstMessageHeaderIn(sourceFolder); const targetFolder = MailHelper.createMailFolder("target-box"); @@ -85,20 +85,29 @@ test(withTestGpgHome(withEnigmail(function messageIsMovedAndDecrypted() { const dispatchedHeader = MailHelper.fetchFirstMessageHeaderIn(targetFolder); Assert.ok(dispatchedHeader !== null); + + let msgUriSpec = dispatchedHeader.folder.getUriForMsg(dispatchedHeader); + const msgSvc = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger).messageServiceFromURI(msgUriSpec); + + let urlObj = {}; + msgSvc.GetUrlForUri(msgUriSpec, urlObj, null); + do_test_pending(); - MsgHdrToMimeMessage( - dispatchedHeader, - null, - function(header, mime) { - Assert.ok(!mime.isEncrypted); - Assert.assertContains(mime.parts[0].body, "This is encrypted"); + EnigmailMime.getMimeTreeFromUrl( + urlObj.value.spec, + true, + function(mimeTree) { + Assert.equal(mimeTree.subParts.length, 1); + if (mimeTree.subParts.length > 0) { + Assert.assertContains(mimeTree.subParts[0].body, "This message is encrypted"); + } do_test_finished(); }, false ); }))); -/* + test(withTestGpgHome(withEnigmail(function messageWithAttachemntIsMovedAndDecrypted() { loadSecretKey(); loadPublicKey(); @@ -118,23 +127,71 @@ test(withTestGpgHome(withEnigmail(function messageWithAttachemntIsMovedAndDecryp const dispatchedHeader = MailHelper.fetchFirstMessageHeaderIn(targetFolder); Assert.ok(dispatchedHeader !== null); + let msgUriSpec = dispatchedHeader.folder.getUriForMsg(dispatchedHeader); + const msgSvc = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger).messageServiceFromURI(msgUriSpec); + + let urlObj = {}; + msgSvc.GetUrlForUri(msgUriSpec, urlObj, null); + do_test_pending(); - MsgHdrToMimeMessage( - dispatchedHeader, - null, - function(header, mime) { - Assert.ok(!mime.isEncrypted); - Assert.assertContains(mime.parts[0].parts[0].body, "This is encrypted"); - const atts = extractAttachments(mime); - Assert.ok(!atts[0].isEncrypted); - Assert.assertContains(atts[0].body, "This is an attachment."); + EnigmailMime.getMimeTreeFromUrl( + urlObj.value.spec, + true, + function(mimeTree) { + Assert.assertContains(mimeTree.subParts[0].body, "This is encrypted"); + Assert.equal(mimeTree.subParts.length, 2); + if (mimeTree.subParts.length >= 2) { + Assert.assertContains(mimeTree.subParts[1].body, "This is an attachment."); + } do_test_finished(); }, false ); }))); -*/ +test(withTestGpgHome(withEnigmail(function messageWithAttachemntIsMovedAndReEncrypted() { + loadSecretKey(); + loadPublicKey(); + MailHelper.cleanMailFolder(MailHelper.getRootFolder()); + const sourceFolder = MailHelper.createMailFolder("source-box"); + MailHelper.loadEmailToMailFolder("resources/encrypted-email-with-attachment.eml", sourceFolder); + + const header = MailHelper.fetchFirstMessageHeaderIn(sourceFolder); + const targetFolder = MailHelper.createMailFolder("target-box"); + const move = true; + copyListener.OnStopCopy = function(statusCode) { + inspector.exitNestedEventLoop(); + }; + + let keyObj = EnigmailKeyRing.getKeyById("0x65537E212DC19025AD38EDB2781617319CE311C4"); + EnigmailPersistentCrypto.dispatchMessages([header], targetFolder.URI, copyListener, move, keyObj); + inspector.enterNestedEventLoop(0); + + const dispatchedHeader = MailHelper.fetchFirstMessageHeaderIn(targetFolder); + Assert.ok(dispatchedHeader !== null); + + let msgUriSpec = dispatchedHeader.folder.getUriForMsg(dispatchedHeader); + const msgSvc = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger).messageServiceFromURI(msgUriSpec); + + let urlObj = {}; + msgSvc.GetUrlForUri(msgUriSpec, urlObj, null); + + do_test_pending(); + EnigmailMime.getMimeTreeFromUrl( + urlObj.value.spec, + true, + function(mimeTree) { + Assert.assertContains(mimeTree.headers._rawHeaders.get("content-type")[0], "multipart/encrypted"); + Assert.assertContains(mimeTree.subParts[0].body, "Version: 1"); + Assert.equal(mimeTree.subParts.length, 2); + if (mimeTree.subParts.length >= 2) { + Assert.assertContains(mimeTree.subParts[1].body, "---BEGIN PGP MESSAGE---"); + } + do_test_finished(); + }, + false + ); +}))); var loadSecretKey = function() { const secretKey = do_get_file("resources/dev-strike.sec", false); diff --git a/package/tests/resources/encrypted-pgpmime-email.eml b/package/tests/resources/encrypted-pgpmime-email.eml new file mode 100644 index 00000000..f3c8800b --- /dev/null +++ b/package/tests/resources/encrypted-pgpmime-email.eml @@ -0,0 +1,44 @@ +To: Tester 2 <[email protected]> +From: Tester 1 <[email protected]> +Message-ID: <[email protected]> +Date: Tue, 19 Jun 2018 23:39:32 +0530 +User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:52.0) + Gecko/20100101 Thunderbird/52.8.0 +MIME-Version: 1.0 +Subject: Encrypted Message +Content-Type: multipart/encrypted; + protocol="application/pgp-encrypted"; + boundary="UxEMgGKKbt9SDjSozkXfqI0l07sqCV5I4" + +This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156) +--UxEMgGKKbt9SDjSozkXfqI0l07sqCV5I4 +Content-Type: application/pgp-encrypted +Content-Description: PGP/MIME version identification + +Version: 1 + +--UxEMgGKKbt9SDjSozkXfqI0l07sqCV5I4 +Content-Type: application/octet-stream; name="encrypted.asc" +Content-Description: OpenPGP encrypted message +Content-Disposition: inline; filename="encrypted.asc" + +-----BEGIN PGP MESSAGE----- + +hQIMA9U1Yju2Dp5xAQ/6AnDaC/oxD65YSD6I1e5JX3vAzOgB0TL4eTPabl/KZJ6K +8eAWiZubWF7i/EWlEXOYZbWycZC0Vo0mUlSiye4EXB/dnY369r/2UIF6kAEW0d7J +LwMbmg0WMcL+liZSa7HyxajYn8Zb7CohNI4l41KjjYGAbKiJNaK442eEvAQ3fuNp +5/KvV10jwS6fGV3caouTa8aCT8r2U5FdxowuYGGvazLCfPI9aTnz84JgeTutZfW5 +B/yBBNDeRxmUEa9c3k+8aH9c5DKYBH4Chp5EMfvlTE/pZdhRd2RcTZsR637FQVgO +FpTHKvOqQNJla4WqGeH9iHOLJh9OAhHkRaiMbbo+072KfwNHqDD4iwjQ04jFIph1 +zImMJg79PTgSsjcTRTdvkQiHaIx7pVW1m212JEl4km1lmT/C6NCHxTYfNLooEAs1 +6YPbaARnbFTTWjJXfEto7s2PZoX1pZ7GG1MNVpAL6J/puUnmVktj+87bJLkxHQVu +wf3Qq+vcFqB5t63quPMbGbXnHh4P9V+ceWhUhNFSutKWcCmeQL4vxCtL9eOphHEb +1YDHb4FpSlpVJyVUmLh7g/1X3IMCS1HKpiIqJHAUu4FfouAprL5CAfFmCeCRMkqs +/4iR3KtpnwRPwNDgY8Gfy7CL0IdvWZdpyLFaFOPr604Kz2JsJwyxbu1opM3acILS +bgFYYz3p3HwqnGdbnch3+5H3XEToOaeXsH51zSUkR/oq/wNXDWKMKiU5hIUkHYq1 +NsC04Y4EHsbpDPo/GqkcdXEx7Dg0sgohqYqoU+iyq+jjyOEZxNEUdboMadxIm879 +nu672xjC+wg8rBvFr8jh +=q7pm +-----END PGP MESSAGE----- + +--UxEMgGKKbt9SDjSozkXfqI0l07sqCV5I4-- |