Merge pull request #61 from whiteout-io/dev/WO-383

[WO-383] decrypt pgp/inline
This commit is contained in:
Tankred Hase 2014-05-13 16:32:07 +02:00
commit e6de5366c9
2 changed files with 156 additions and 35 deletions

View File

@ -382,8 +382,38 @@ define(function(require) {
root = signedPart.content; root = signedPart.content;
} }
// if the message is plain text and contains pgp/inline, we are only interested in the encrypted
// content, the rest (corporate mail footer, attachments, etc.) is discarded.
var body = _.pluck(self._emailSync.filterBodyParts(root, 'text'), 'content').join('\n');
/*
* here's how the regex works:
* - any content before the PGP block will be discarded
* - "-----BEGIN PGP MESSAGE-----" must be at the beginning (and end) of a line
* - "-----END PGP MESSAGE-----" must be at the beginning (and end) of a line
* - the regex must not match a pgp block in a plain text reply or forward of a pgp/inline message.
* (the encryption will break for replies/forward, because "> " corrupts the PGP block with non-radix-64 characters)
*/
var match = body.match(/^-{5}BEGIN PGP MESSAGE-{5}$[^>]*^-{5}END PGP MESSAGE-{5}$/im);
if (match) {
// show the plain text content
message.body = match[0];
// - replace the bodyParts info with an artificial bodyPart of type "encrypted"
// - _isPgpInline is only used internally to avoid trying to parse non-MIME text with the mailreader
// - set the encrypted flag so we can signal the ui that we're handling encrypted content
message.encrypted = true;
message.bodyParts = [{
type: 'encrypted',
content: match[0],
_isPgpInline: true
}];
done();
return;
}
message.attachments = self._emailSync.filterBodyParts(root, 'attachment'); message.attachments = self._emailSync.filterBodyParts(root, 'attachment');
message.body = _.pluck(self._emailSync.filterBodyParts(root, 'text'), 'content').join('\n'); message.body = body;
message.html = _.pluck(self._emailSync.filterBodyParts(root, 'html'), 'content').join('\n'); message.html = _.pluck(self._emailSync.filterBodyParts(root, 'html'), 'content').join('\n');
done(); done();
@ -439,14 +469,23 @@ define(function(require) {
var encryptedNode = self._emailSync.filterBodyParts(message.bodyParts, 'encrypted')[0]; var encryptedNode = self._emailSync.filterBodyParts(message.bodyParts, 'encrypted')[0];
self._crypto.decrypt(encryptedNode.content, senderPublicKey.publicKey, function(err, decrypted) { self._crypto.decrypt(encryptedNode.content, senderPublicKey.publicKey, function(err, decrypted) {
if (err || !decrypted) { if (err || !decrypted) {
showError(err.errMsg || err.message); showError(err.errMsg || err.message || 'An error occurred during the decryption.');
return;
}
// if the encrypted node contains pgp/inline, we must not parse it
// with the mailreader as it is not well-formed MIME
if (encryptedNode._isPgpInline) {
message.body = decrypted;
message.decrypted = true;
done();
return; return;
} }
// the mailparser works on the .raw property // the mailparser works on the .raw property
encryptedNode.raw = decrypted; encryptedNode.raw = decrypted;
// parse the decrpyted raw content in the mailparser // parse the decrypted raw content in the mailparser
self._mailreader.parse({ self._mailreader.parse({
bodyParts: [encryptedNode] bodyParts: [encryptedNode]
}, function(err, parsedBodyParts) { }, function(err, parsedBodyParts) {
@ -467,7 +506,6 @@ define(function(require) {
message.decrypted = true; message.decrypted = true;
// we're done here! // we're done here!
done(); done();
}); });

View File

@ -21,6 +21,7 @@ define(function(require) {
var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail, var emailAddress, passphrase, asymKeySize, mockkeyId, dummyEncryptedMail,
dummyDecryptedMail, mockKeyPair, account, verificationMail, verificationUuid, dummyDecryptedMail, mockKeyPair, account, verificationMail, verificationUuid,
corruptedVerificationMail, corruptedVerificationUuid, corruptedVerificationMail, corruptedVerificationUuid,
localListStub, localStoreStub, imapGetStub,
nonWhitelistedMail; nonWhitelistedMail;
beforeEach(function(done) { beforeEach(function(done) {
@ -122,6 +123,10 @@ define(function(require) {
dao = new EmailDAO(keychainStub, pgpStub, devicestorageStub, pgpBuilderStub, mailreader, emailSync); dao = new EmailDAO(keychainStub, pgpStub, devicestorageStub, pgpBuilderStub, mailreader, emailSync);
dao._account = account; dao._account = account;
localListStub = sinon.stub(emailSync, '_localListMessages');
localStoreStub = sinon.stub(emailSync, '_localStoreMessages');
imapGetStub = sinon.stub(emailSync, '_getBodyParts');
expect(dao._keychain).to.equal(keychainStub); expect(dao._keychain).to.equal(keychainStub);
expect(dao._crypto).to.equal(pgpStub); expect(dao._crypto).to.equal(pgpStub);
expect(dao._devicestorage).to.equal(devicestorageStub); expect(dao._devicestorage).to.equal(devicestorageStub);
@ -606,8 +611,7 @@ define(function(require) {
describe('getBody', function() { describe('getBody', function() {
var folder = 'asdasdasdasdasd', var folder = 'asdasdasdasdasd',
uid = 1234, uid = 1234;
localListStub, localStoreStub, imapGetStub;
it('should not do anything if the message already has content', function() { it('should not do anything if the message already has content', function() {
var message = { var message = {
@ -629,7 +633,7 @@ define(function(require) {
uid: uid uid: uid
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').withArgs({ localListStub.withArgs({
folder: folder, folder: folder,
uid: uid uid: uid
}).yieldsAsync(null, [{ }).yieldsAsync(null, [{
@ -656,7 +660,7 @@ define(function(require) {
expect(message.loadingBody).to.be.true; expect(message.loadingBody).to.be.true;
}); });
it('should read an encrypted body from the device', function(done) { it('should read a pgp/mime from the device', function(done) {
var message, ct, pt; var message, ct, pt;
pt = 'bender is great!'; pt = 'bender is great!';
@ -666,7 +670,7 @@ define(function(require) {
encrypted: true encrypted: true
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').withArgs({ localListStub.withArgs({
folder: folder, folder: folder,
uid: uid uid: uid
}).yieldsAsync(null, [{ }).yieldsAsync(null, [{
@ -697,6 +701,48 @@ define(function(require) {
expect(message.loadingBody).to.be.true; expect(message.loadingBody).to.be.true;
}); });
it('should read a pgp/inline from the device', function(done) {
var message, ct, pt;
ct = '-----BEGIN PGP MESSAGE-----\nasdasdasd\n-----END PGP MESSAGE-----';
pt = 'bla bla yadda yadda';
message = {
uid: uid
};
localListStub.yieldsAsync(null, [{
bodyParts: [{
type: 'text',
content: pt
}, {
type: 'text',
content: ct
}, {
type: 'text',
content: pt
}]
}]);
dao.getBody({
message: message,
folder: folder
}, function(err, msg) {
expect(err).to.not.exist;
expect(msg).to.equal(message);
expect(msg.body).to.equal(ct);
expect(msg.bodyParts[0].type).to.equal('encrypted');
expect(msg.bodyParts[0].content).to.equal(ct);
expect(msg.encrypted).to.be.true;
expect(message.loadingBody).to.be.false;
expect(localListStub.calledOnce).to.be.true;
done();
});
expect(message.loadingBody).to.be.true;
});
it('should stream from imap and set plain text body', function(done) { it('should stream from imap and set plain text body', function(done) {
var message, body; var message, body;
@ -710,17 +756,17 @@ define(function(require) {
}] }]
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').withArgs({ localListStub.withArgs({
folder: folder, folder: folder,
uid: uid uid: uid
}).yieldsAsync(null, [message]); }).yieldsAsync(null, [message]);
localStoreStub = sinon.stub(emailSync, '_localStoreMessages').withArgs({ localStoreStub.withArgs({
folder: folder, folder: folder,
emails: [message] emails: [message]
}).yieldsAsync(); }).yieldsAsync();
imapGetStub = sinon.stub(emailSync, '_getBodyParts').withArgs({ imapGetStub.withArgs({
folder: folder, folder: folder,
uid: message.uid, uid: message.uid,
bodyParts: message.bodyParts bodyParts: message.bodyParts
@ -763,17 +809,17 @@ define(function(require) {
}] }]
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').withArgs({ localListStub.withArgs({
folder: folder, folder: folder,
uid: uid uid: uid
}).yieldsAsync(null, [message]); }).yieldsAsync(null, [message]);
localStoreStub = sinon.stub(emailSync, '_localStoreMessages').withArgs({ localStoreStub.withArgs({
folder: folder, folder: folder,
emails: [message] emails: [message]
}).yieldsAsync(); }).yieldsAsync();
imapGetStub = sinon.stub(emailSync, '_getBodyParts').withArgs({ imapGetStub.withArgs({
folder: folder, folder: folder,
uid: message.uid, uid: message.uid,
bodyParts: message.bodyParts bodyParts: message.bodyParts
@ -814,9 +860,9 @@ define(function(require) {
}] }]
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').yieldsAsync(null, [message]); localListStub.yieldsAsync(null, [message]);
localStoreStub = sinon.stub(emailSync, '_localStoreMessages').yieldsAsync({}); localStoreStub.yieldsAsync({});
imapGetStub = sinon.stub(emailSync, '_getBodyParts').yieldsAsync(null, [{ imapGetStub.yieldsAsync(null, [{
type: 'text', type: 'text',
content: 'bender is great! bender is great!' content: 'bender is great! bender is great!'
}]); }]);
@ -845,9 +891,8 @@ define(function(require) {
}] }]
}; };
localListStub = sinon.stub(emailSync, '_localListMessages').yields(null, [message]); localListStub.yields(null, [message]);
imapGetStub = sinon.stub(emailSync, '_getBodyParts').yieldsAsync({}); imapGetStub.yieldsAsync({});
localStoreStub = sinon.stub(emailSync, '_localStoreMessages');
dao.getBody({ dao.getBody({
message: message, message: message,
@ -924,6 +969,16 @@ define(function(require) {
}); });
describe('decryptBody', function() { describe('decryptBody', function() {
var parseStub;
beforeEach(function() {
parseStub = sinon.stub(mailreader, 'parse');
});
afterEach(function() {
mailreader.parse.restore();
});
it('should do nothing when the message is not encrypted', function() { it('should do nothing when the message is not encrypted', function() {
var message = { var message = {
encrypted: false, encrypted: false,
@ -974,7 +1029,7 @@ define(function(require) {
}); });
it('decrypt a pgp/mime message', function(done) { it('decrypt a pgp/mime message', function(done) {
var message, ct, pt, parsed, parseStub; var message, ct, pt, parsed;
pt = 'bender is great'; pt = 'bender is great';
ct = '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----'; ct = '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----';
@ -991,10 +1046,9 @@ define(function(require) {
}] }]
}; };
keychainStub.getReceiverPublicKey.withArgs(message.from[0].address).yieldsAsync(null, mockKeyPair.publicKey); keychainStub.getReceiverPublicKey.withArgs(message.from[0].address).yieldsAsync(null, mockKeyPair.publicKey);
pgpStub.decrypt.withArgs(ct, mockKeyPair.publicKey.publicKey).yieldsAsync(null, pt); pgpStub.decrypt.withArgs(ct, mockKeyPair.publicKey.publicKey).yieldsAsync(null, pt);
parseStub = sinon.stub(mailreader, 'parse').withArgs({ parseStub.withArgs({
bodyParts: [{ bodyParts: [{
type: 'encrypted', type: 'encrypted',
content: ct, content: ct,
@ -1012,17 +1066,54 @@ define(function(require) {
message: message message: message
}, function(error, msg) { }, function(error, msg) {
expect(error).to.not.exist; expect(error).to.not.exist;
expect(msg).to.equal(message); expect(msg).to.equal(message);
expect(message.decrypted).to.be.true; expect(message.decrypted).to.be.true;
expect(message.body).to.equal(parsed); expect(message.body).to.equal(parsed);
expect(message.decryptingBody).to.be.false; expect(message.decryptingBody).to.be.false;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true; expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(parseStub.calledOnce).to.be.true; expect(parseStub.calledOnce).to.be.true;
mailreader.parse.restore(); done();
});
expect(message.decryptingBody).to.be.true;
});
it('decrypt a pgp/inline message', function(done) {
var message, ct, pt;
pt = 'bender is great';
ct = '-----BEGIN PGP MESSAGE-----asdasdasd-----END PGP MESSAGE-----';
message = {
from: [{
address: 'asdasdasd'
}],
body: ct,
encrypted: true,
bodyParts: [{
type: 'encrypted',
content: ct,
_isPgpInline: true
}]
};
keychainStub.getReceiverPublicKey.withArgs(message.from[0].address).yieldsAsync(null, mockKeyPair.publicKey);
pgpStub.decrypt.withArgs(ct, mockKeyPair.publicKey.publicKey).yieldsAsync(null, pt);
dao.decryptBody({
message: message
}, function(error, msg) {
expect(error).to.not.exist;
expect(msg).to.equal(message);
expect(message.decrypted).to.be.true;
expect(message.body).to.equal(pt);
expect(message.decryptingBody).to.be.false;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(parseStub.called).to.be.false;
done(); done();
}); });
@ -1042,7 +1133,6 @@ define(function(require) {
}] }]
}; };
var parseStub = sinon.spy(mailreader, 'parse');
keychainStub.getReceiverPublicKey.yields(null, mockKeyPair.publicKey); keychainStub.getReceiverPublicKey.yields(null, mockKeyPair.publicKey);
pgpStub.decrypt.yields({ pgpStub.decrypt.yields({
errMsg: 'asd' errMsg: 'asd'
@ -1055,12 +1145,10 @@ define(function(require) {
expect(msg.body).to.equal('asd'); expect(msg.body).to.equal('asd');
expect(msg).to.exist; expect(msg).to.exist;
expect(message.decryptingBody).to.be.false; expect(message.decryptingBody).to.be.false;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.calledOnce).to.be.true; expect(pgpStub.decrypt.calledOnce).to.be.true;
expect(parseStub.called).to.be.false; expect(parseStub.called).to.be.false;
mailreader.parse.restore();
done(); done();
}); });
}); });
@ -1078,23 +1166,18 @@ define(function(require) {
}] }]
}; };
var parseStub = sinon.spy(mailreader, 'parse');
keychainStub.getReceiverPublicKey.yields({}); keychainStub.getReceiverPublicKey.yields({});
dao.decryptBody({ dao.decryptBody({
message: message message: message
}, function(error, msg) { }, function(error, msg) {
expect(error).to.exist; expect(error).to.exist;
expect(msg).to.not.exist; expect(msg).to.not.exist;
expect(message.decryptingBody).to.be.false; expect(message.decryptingBody).to.be.false;
expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true;
expect(pgpStub.decrypt.called).to.be.false; expect(pgpStub.decrypt.called).to.be.false;
expect(parseStub.called).to.be.false; expect(parseStub.called).to.be.false;
mailreader.parse.restore();
done(); done();
}); });
}); });