From 0d1f0000de4edfb79399a2e2d38ba3bef50b2da0 Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Sat, 18 Jan 2014 11:42:28 +0100 Subject: [PATCH] add pgp parsing capability --- src/js/dao/email-dao.js | 27 ++++++-- test/new-unit/email-dao-test.js | 119 ++++++++++++++++++++++++++++++-- 2 files changed, 132 insertions(+), 14 deletions(-) diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index 6486b60..1c6b85f 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -798,7 +798,8 @@ define(function(require) { if (!senderPubkey) { // this should only happen if a mail from another channel is in the inbox - setBodyAndContinue('Public key for sender not found!'); + email.body = 'Public key for sender not found!'; + localCallback(null, email); return; } @@ -810,7 +811,20 @@ define(function(require) { // set encrypted flag email.encrypted = true; - setBodyAndContinue(decrypted); + + // does our message block even need to be parsed? + if (decrypted.indexOf('Content-Type: multipart/signed') === -1) { + // decrypted message is plain text and not a well-formed email + email.body = decrypted; + localCallback(null, email); + return; + } + + // parse decrypted message + self._imapParseMessageBlock({ + message: email, + block: decrypted + }, localCallback); }); }); @@ -821,11 +835,6 @@ define(function(require) { // parse email body for encrypted message block email.body = email.body.substring(start, end); } - - function setBodyAndContinue(text) { - email.body = text; - localCallback(null, email); - } } }; @@ -1074,6 +1083,10 @@ define(function(require) { }, callback); }; + EmailDAO.prototype._imapParseMessageBlock = function(options, callback) { + this._imapClient.parseDecryptedMessageBlock(options, callback); + }; + /** * Get an email messsage including the email body from imap * @param {String} options.messageId The diff --git a/test/new-unit/email-dao-test.js b/test/new-unit/email-dao-test.js index 28bfb33..4609471 100644 --- a/test/new-unit/email-dao-test.js +++ b/test/new-unit/email-dao-test.js @@ -14,7 +14,7 @@ define(function(require) { var dao, keychainStub, imapClientStub, smtpClientStub, pgpStub, devicestorageStub; var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail, - dummyDecryptedMail, mockKeyPair, account, publicKey, verificationMail, verificationUuid, + dummyDecryptedMail, dummyLegacyDecryptedMail, mockKeyPair, account, publicKey, verificationMail, verificationUuid, corruptedVerificationMail, corruptedVerificationUuid, nonWhitelistedMail; @@ -65,6 +65,20 @@ define(function(require) { answered: false }; dummyDecryptedMail = { + uid: 1234, + from: [{ + address: 'asd@asd.de' + }], + to: [{ + address: 'qwe@qwe.de' + }], + subject: 'qweasd', + body: 'Content-Type: multipart/signed;\r\n boundary="Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2";\r\n protocol="application/pgp-signature";\r\n micalg=pgp-sha512\r\n\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2\r\nContent-Type: multipart/mixed;\r\n boundary="Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA"\r\n\r\n\r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA\r\nContent-Transfer-Encoding: 7bit\r\nContent-Type: text/plain;\r\n charset=us-ascii\r\n\r\nasdasd \r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA\r\nContent-Disposition: attachment;\r\n filename=dummy.txt\r\nContent-Type: text/plain;\r\n name="dummy.txt"\r\nContent-Transfer-Encoding: 7bit\r\n\r\noaudbcoaurbvosuabvlasdjbfalwubjvawvb\r\n--Apple-Mail=_8ED7DC84-6AD9-4A08-8327-80B62D6BCBFA--\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2\r\nContent-Transfer-Encoding: 7bit\r\nContent-Disposition: attachment;\r\n filename=signature.asc\r\nContent-Type: application/pgp-signature;\r\n name=signature.asc\r\nContent-Description: Message signed with OpenPGP using GPGMail\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\nComment: GPGTools - https://gpgtools.org\r\n\r\niQEcBAEBCgAGBQJS2kO1AAoJEDzmUwH7XO/cP+YH/2PSBxX1ZZd83Uf9qBGDY807\r\niHOdgPFXm64YjSnohO7XsPcnmihqP1ipS2aaCXFC3/Vgb9nc4isQFS+i1VdPwfuR\r\n1Pd2l3dC4/nD4xO9h/W6JW7Yd24NS5TJD5cA7LYwQ8LF+rOzByMatiTMmecAUCe8\r\nEEalEjuogojk4IacA8dg/bfLqQu9E+0GYUJBcI97dx/0jZ0qMOxbWOQLsJ3DnUnV\r\nOad7pAIbHEO6T0EBsH7TyTj4RRHkP6SKE0mm6ZYUC7KCk2Z3MtkASTxUrnqW5qZ5\r\noaXUO9GEc8KZcmbCdhZY2Y5h+dmucaO0jpbeSKkvtYyD4KZrSvt7NTb/0dSLh4Y=\r\n=G8km\r\n-----END PGP SIGNATURE-----\r\n\r\n--Apple-Mail=_1D8756C0-F347-4D7A-A8DB-7869CBF14FD2--\r\n', + unread: false, + answered: false, + receiverKeys: ['-----BEGIN PGP PUBLIC KEY-----\nasd\n-----END PGP PUBLIC KEY-----'] + }; + dummyLegacyDecryptedMail = { uid: 1234, from: [{ address: 'asd@asd.de' @@ -471,6 +485,19 @@ define(function(require) { }); }); + describe('_imapParseMessageBlock', function() { + it('should parse a message', function(done) { + imapClientStub.parseDecryptedMessageBlock.yields(null, {}); + + dao._imapParseMessageBlock(function(err, msg) { + expect(err).to.not.exist; + expect(msg).to.exist; + done(); + }); + + }); + }); + describe('_imapLogin', function() { it('should fail when disconnected', function(done) { dao.onDisconnect(null, function(err) { @@ -823,7 +850,7 @@ define(function(require) { describe('sync', function() { it('should work initially', function(done) { - var folder, localListStub, invocations, imapSearchStub; + var folder, localListStub, invocations, imapSearchStub, imapParseStub; invocations = 0; folder = 'FOLDAAAA'; @@ -852,6 +879,13 @@ define(function(require) { answered: true }).yields(null, []); + imapParseStub = sinon.stub(dao, '_imapParseMessageBlock'); + imapParseStub.withArgs({ + message: dummyEncryptedMail, + block: dummyDecryptedMail.body + }).yields(null, dummyDecryptedMail); + + dao.sync({ folder: folder }, function(err) { @@ -870,6 +904,7 @@ define(function(require) { expect(pgpStub.decrypt.calledOnce).to.be.true; expect(imapSearchStub.calledThrice).to.be.true; expect(dao._account.folders[0].count).to.equal(1); + expect(imapParseStub.calledOnce).to.be.true; done(); }); @@ -903,7 +938,7 @@ define(function(require) { }); it('should initially sync downstream when storage is empty', function(done) { - var folder, localListStub, localStoreStub, invocations, imapSearchStub, imapGetStub; + var folder, localListStub, localStoreStub, invocations, imapSearchStub, imapGetStub, imapParseStub; invocations = 0; folder = 'FOLDAAAA'; @@ -912,8 +947,8 @@ define(function(require) { path: folder }]; - dummyEncryptedMail.unread = true; - dummyEncryptedMail.answered = true; + dummyDecryptedMail.unread = true; + dummyDecryptedMail.answered = true; localListStub = sinon.stub(dao, '_localListMessages').withArgs({ folder: folder @@ -938,6 +973,9 @@ define(function(require) { answered: true }).yields(null, [dummyEncryptedMail.uid]); + imapParseStub = sinon.stub(dao, '_imapParseMessageBlock'); + imapParseStub.yields(null, dummyDecryptedMail); + localStoreStub = sinon.stub(dao, '_localStoreMessages').yields(); dao.sync({ @@ -960,6 +998,7 @@ define(function(require) { expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(pgpStub.decrypt.calledOnce).to.be.true; expect(dao._account.folders[0].count).to.equal(1); + expect(imapParseStub.calledOnce).to.be.true; done(); }); @@ -1162,7 +1201,7 @@ define(function(require) { }); }); - it('should error whilte removing messages from local', function(done) { + it('should error while removing messages from local', function(done) { var invocations, folder, localListStub, imapSearchStub, localDeleteStub, imapDeleteStub; invocations = 0; @@ -1316,7 +1355,7 @@ define(function(require) { }); }); - it('should fetch messages downstream from the remote', function(done) { + it('should fetch legacy messages downstream from the remote', function(done) { var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub; invocations = 0; @@ -1348,10 +1387,75 @@ define(function(require) { uid: dummyEncryptedMail.uid }).yields(null, dummyEncryptedMail); + localStoreStub = sinon.stub(dao, '_localStoreMessages').yields(); + keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair); + pgpStub.decrypt.withArgs(dummyEncryptedMail.body, mockKeyPair.publicKey).yields(null, dummyLegacyDecryptedMail.body); + + + dao.sync({ + folder: folder + }, function(err) { + expect(err).to.not.exist; + + if (invocations === 0) { + expect(dao._account.busy).to.be.true; + invocations++; + return; + } + + expect(dao._account.busy).to.be.false; + expect(dao._account.folders[0].messages).to.not.be.empty; + expect(localListStub.calledOnce).to.be.true; + expect(imapSearchStub.calledThrice).to.be.true; + expect(imapGetStub.calledOnce).to.be.true; + expect(localStoreStub.calledOnce).to.be.true; + expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; + expect(pgpStub.decrypt.calledOnce).to.be.true; + done(); + }); + }); + + it('should fetch valid pgp messages downstream from the remote', function(done) { + var invocations, folder, localListStub, imapSearchStub, imapGetStub, localStoreStub, imapParseStub; + + invocations = 0; + folder = 'FOLDAAAA'; + dao._account.folders = [{ + type: 'Folder', + path: folder, + messages: [] + }]; + + localListStub = sinon.stub(dao, '_localListMessages').withArgs({ + folder: folder + }).yields(null, []); + imapSearchStub = sinon.stub(dao, '_imapSearch'); + imapSearchStub.withArgs({ + folder: folder + }).yields(null, [dummyEncryptedMail.uid]); + imapSearchStub.withArgs({ + folder: folder, + unread: true + }).yields(null, []); + imapSearchStub.withArgs({ + folder: folder, + answered: true + }).yields(null, []); + + imapGetStub = sinon.stub(dao, '_imapGetMessage').withArgs({ + folder: folder, + uid: dummyEncryptedMail.uid + }).yields(null, dummyEncryptedMail); + localStoreStub = sinon.stub(dao, '_localStoreMessages').yields(); keychainStub.getReceiverPublicKey.withArgs(dummyEncryptedMail.from[0].address).yields(null, mockKeyPair); pgpStub.decrypt.withArgs(dummyEncryptedMail.body, mockKeyPair.publicKey).yields(null, dummyDecryptedMail.body); + imapParseStub = sinon.stub(dao, '_imapParseMessageBlock'); + imapParseStub.withArgs({ + message: dummyEncryptedMail, + block: dummyDecryptedMail.body + }).yields(null, dummyDecryptedMail); dao.sync({ folder: folder @@ -1372,6 +1476,7 @@ define(function(require) { expect(localStoreStub.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(pgpStub.decrypt.calledOnce).to.be.true; + expect(imapParseStub.calledOnce).to.be.true; done(); }); });