1
0
mirror of https://github.com/moparisthebest/mail synced 2024-12-22 15:28:49 -05:00

Merge pull request #77 from whiteout-io/dev/WO-292

Added integration tests for Apple Mail and Thunderbird
This commit is contained in:
Felix Hammerl 2014-06-27 17:54:28 +02:00
commit 0cd4430103
6 changed files with 406 additions and 41 deletions

View File

@ -277,10 +277,11 @@ define(function(require) {
}; };
/** /**
* Decrypt and verify a pgp message for a single sender * Decrypt and verify a pgp message for a single sender.
* You need to check if signatures are both present and valid in the callback!
*/ */
PGP.prototype.decrypt = function(ciphertext, publicKeyArmored, callback) { PGP.prototype.decrypt = function(ciphertext, publicKeyArmored, callback) {
var publicKeys, message, signaturesValid; var publicKeys, message, signaturesValid, signaturesPresent;
// check keys // check keys
if (!this._privateKey || !publicKeyArmored) { if (!this._privateKey || !publicKeyArmored) {
@ -308,18 +309,15 @@ define(function(require) {
// check if signatures are valid // check if signatures are valid
signaturesValid = true; signaturesValid = true;
signaturesPresent = !!decrypted.signatures.length;
decrypted.signatures.forEach(function(sig) { decrypted.signatures.forEach(function(sig) {
if (!sig.valid) { if (!sig.valid) {
signaturesValid = false; signaturesValid = false;
} }
}); });
if (!signaturesValid) {
callback(new Error('Verifying PGP signature failed!'));
return;
}
// return decrypted plaintext // return decrypted plaintext
callback(null, decrypted.text); callback(null, decrypted.text, signaturesPresent, signaturesValid);
} }
}; };

View File

@ -707,7 +707,7 @@ define(function(require) {
if (message.signed) { if (message.signed) {
var signedPart = filterBodyParts(message.bodyParts, 'signed')[0]; var signedPart = filterBodyParts(message.bodyParts, 'signed')[0];
message.message = signedPart.message; message.signedMessage = signedPart.signedMessage;
message.signature = signedPart.signature; message.signature = signedPart.signature;
// TODO check integrity // TODO check integrity
// in case of a signed message, you only want to show the signed content and ignore the rest // in case of a signed message, you only want to show the signed content and ignore the rest
@ -719,17 +719,18 @@ define(function(require) {
var body = _.pluck(filterBodyParts(root, 'text'), 'content').join('\n'); var body = _.pluck(filterBodyParts(root, 'text'), 'content').join('\n');
/* /*
* here's how the regex works: * here's how the pgp/inline regex works:
* - any content before the PGP block will be discarded * - any content before the PGP block will be discarded
* - "-----BEGIN PGP MESSAGE-----" must be at the beginning (and end) of a line * - "-----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 * - "-----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 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) * (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); var pgpInlineRegex = /^-{5}BEGIN PGP MESSAGE-{5}$[^>]*^-{5}END PGP MESSAGE-{5}$/im;
if (match) { var pgpInlineMatch = pgpInlineRegex.exec(body);
if (pgpInlineMatch) {
// show the plain text content // show the plain text content
message.body = match[0]; message.body = pgpInlineMatch[0];
// - replace the bodyParts info with an artificial bodyPart of type "encrypted" // - 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 // - _isPgpInline is only used internally to avoid trying to parse non-MIME text with the mailreader
@ -737,15 +738,35 @@ define(function(require) {
message.encrypted = true; message.encrypted = true;
message.bodyParts = [{ message.bodyParts = [{
type: 'encrypted', type: 'encrypted',
content: match[0], content: pgpInlineMatch[0],
_isPgpInline: true _isPgpInline: true
}]; }];
done(); done();
return; return;
} }
message.attachments = filterBodyParts(root, 'attachment');
/*
* here's how the clear signing regex works:
* - any content before the PGP block will be discarded
* - "-----BEGIN PGP SIGNED MESSAGE-----" must be at the beginning (and end) of a line
* - "-----END PGP SIGNATURE-----" 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/signed message.
* (the encryption will break for replies/forward, because "> " corrupts the PGP block with non-radix-64 characters)
*/
var clearSignedRegex = /^-{5}BEGIN PGP SIGNED MESSAGE-{5}[\s\S]*\n{2}([\S\s]*)(-{5}BEGIN PGP SIGNATURE-{5}[\S\s]*-{5}END PGP SIGNATURE-{5})$/im;
var clearSignedMatch = clearSignedRegex.exec(body);
if (clearSignedMatch) {
message.clearSignedMessage = clearSignedMatch[0];
message.signed = true;
// TODO check integrity
message.body = clearSignedMatch[1].trim();
} else {
message.body = body; message.body = body;
}
message.attachments = filterBodyParts(root, 'attachment');
message.html = _.pluck(filterBodyParts(root, 'html'), 'content').join('\n'); message.html = _.pluck(filterBodyParts(root, 'html'), 'content').join('\n');
inlineExternalImages(message); inlineExternalImages(message);
@ -818,10 +839,13 @@ define(function(require) {
// get the receiver's public key to check the message signature // get the receiver's public key to check the message signature
var encryptedNode = filterBodyParts(message.bodyParts, 'encrypted')[0]; var encryptedNode = filterBodyParts(message.bodyParts, 'encrypted')[0];
self._pgp.decrypt(encryptedNode.content, senderPublicKey.publicKey, function(err, decrypted) { self._pgp.decrypt(encryptedNode.content, senderPublicKey.publicKey, function(err, decrypted, signaturesPresent, signaturesValid) {
if (err || !decrypted) { if (err || !decrypted) {
showError(err.errMsg || err.message || 'An error occurred during the decryption.'); return showError(err.message || 'An error occurred during the decryption.');
return; }
if (signaturesPresent && !signaturesValid) {
return callback(new Error('Could not verifying the authenticity of this message because PGP signature check failed! This message may have been tampered with!'));
} }
// if the encrypted node contains pgp/inline, we must not parse it // if the encrypted node contains pgp/inline, we must not parse it
@ -847,7 +871,6 @@ define(function(require) {
// we have successfully interpreted the descrypted message, // we have successfully interpreted the descrypted message,
// so let's update the views on the message parts // so let's update the views on the message parts
message.body = _.pluck(filterBodyParts(parsedBodyParts, 'text'), 'content').join('\n'); message.body = _.pluck(filterBodyParts(parsedBodyParts, 'text'), 'content').join('\n');
message.html = _.pluck(filterBodyParts(parsedBodyParts, 'html'), 'content').join('\n'); message.html = _.pluck(filterBodyParts(parsedBodyParts, 'html'), 'content').join('\n');
message.attachments = _.reject(filterBodyParts(parsedBodyParts, 'attachment'), function(attmt) { message.attachments = _.reject(filterBodyParts(parsedBodyParts, 'attachment'), function(attmt) {
@ -856,6 +879,9 @@ define(function(require) {
}); });
inlineExternalImages(message); inlineExternalImages(message);
// if the decryption worked and signatures are present, everything's fine.
// no error is thrown if signatures are not present
message.signed = signaturesPresent;
message.decrypted = true; message.decrypted = true;
// we're done here! // we're done here!

View File

@ -377,10 +377,9 @@ define(function(require) {
// decrypt the session key // decrypt the session key
var ct = regSessionKey.encryptedRegSessionKey; var ct = regSessionKey.encryptedRegSessionKey;
self._pgp.decrypt(ct, serverPubkey.publicKey, function(err, decrypedSessionKey) { self._pgp.decrypt(ct, serverPubkey.publicKey, function(err, decrypedSessionKey, signaturesPresent, signaturesValid) {
if (err) { if (err || !(/*signaturesPresent &&*/ signaturesValid)) {
callback(err); return callback(err || new Error('Verifying PGP signature failed!'));
return;
} }
uploadDeviceSecret(decrypedSessionKey); uploadDeviceSecret(decrypedSessionKey);
@ -465,18 +464,16 @@ define(function(require) {
// decrypt the session key // decrypt the session key
var ct1 = authSessionKey.encryptedAuthSessionKey; var ct1 = authSessionKey.encryptedAuthSessionKey;
self._pgp.decrypt(ct1, serverPubkey.publicKey, function(err, decryptedSessionKey) { self._pgp.decrypt(ct1, serverPubkey.publicKey, function(err, decryptedSessionKey, signaturesPresent, signaturesValid) {
if (err) { if (err || !(/*signaturesPresent &&*/ signaturesValid)) {
callback(err); return callback(err || new Error('Verifying PGP signature failed!'));
return;
} }
// decrypt the challenge // decrypt the challenge
var ct2 = authSessionKey.encryptedChallenge; var ct2 = authSessionKey.encryptedChallenge;
self._pgp.decrypt(ct2, serverPubkey.publicKey, function(err, decryptedChallenge) { self._pgp.decrypt(ct2, serverPubkey.publicKey, function(err, decryptedChallenge, signaturesPresent, signaturesValid) {
if (err) { if (err || !(/*signaturesPresent &&*/ signaturesValid)) {
callback(err); return callback(err || new Error('Verifying PGP signature failed!'));
return;
} }
encryptChallenge(decryptedSessionKey, decryptedChallenge); encryptChallenge(decryptedSessionKey, decryptedChallenge);

File diff suppressed because one or more lines are too long

View File

@ -1326,15 +1326,13 @@ define(function(require) {
}; };
keychainStub.getReceiverPublicKey.yieldsAsync(null, mockKeyPair.publicKey); keychainStub.getReceiverPublicKey.yieldsAsync(null, mockKeyPair.publicKey);
pgpStub.decrypt.yieldsAsync({ pgpStub.decrypt.yieldsAsync(new Error('fail.'));
errMsg: 'asd'
});
dao.decryptBody({ dao.decryptBody({
message: message message: message
}, function(error, msg) { }, function(error, msg) {
expect(error).to.not.exist; expect(error).to.not.exist;
expect(msg.body).to.equal('asd'); expect(msg.body).to.equal('fail.');
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;

View File

@ -799,7 +799,7 @@ define(function(require) {
lookupPublicKeyStub.yields(null, { lookupPublicKeyStub.yields(null, {
publicKey: 'pubkey' publicKey: 'pubkey'
}); });
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted'); pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted', true, true);
getDeviceSecretStub.yields(42); getDeviceSecretStub.yields(42);
keychainDao.registerDevice({ keychainDao.registerDevice({
@ -823,7 +823,7 @@ define(function(require) {
lookupPublicKeyStub.yields(null, { lookupPublicKeyStub.yields(null, {
publicKey: 'pubkey' publicKey: 'pubkey'
}); });
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted'); pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted', true, true);
getDeviceSecretStub.yields(null, 'secret'); getDeviceSecretStub.yields(null, 'secret');
cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(42); cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(42);
@ -848,7 +848,7 @@ define(function(require) {
lookupPublicKeyStub.yields(null, { lookupPublicKeyStub.yields(null, {
publicKey: 'pubkey' publicKey: 'pubkey'
}); });
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted'); pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted', true, true);
getDeviceSecretStub.yields(null, 'secret'); getDeviceSecretStub.yields(null, 'secret');
cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(null, 'encryptedDeviceSecret'); cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(null, 'encryptedDeviceSecret');
privkeyDaoStub.uploadDeviceSecret.yields(); privkeyDaoStub.uploadDeviceSecret.yields();
@ -987,7 +987,7 @@ define(function(require) {
publickKey: 'publicKey' publickKey: 'publicKey'
}); });
pgpStub.decrypt.yields(null, 'decryptedStuff'); pgpStub.decrypt.yields(null, 'decryptedStuff', true, true);
getDeviceSecretStub.yields(null, 'deviceSecret'); getDeviceSecretStub.yields(null, 'deviceSecret');
cryptoStub.encrypt.yields(null, 'encryptedStuff'); cryptoStub.encrypt.yields(null, 'encryptedStuff');
privkeyDaoStub.verifyAuthentication.yields(42); privkeyDaoStub.verifyAuthentication.yields(42);
@ -1008,7 +1008,7 @@ define(function(require) {
lookupPublicKeyStub.yields(); lookupPublicKeyStub.yields();
pgpStub.decrypt.yields(null, 'decryptedStuff'); pgpStub.decrypt.yields(null, 'decryptedStuff', true, true);
getDeviceSecretStub.yields(null, 'deviceSecret'); getDeviceSecretStub.yields(null, 'deviceSecret');
cryptoStub.encrypt.yields(null, 'encryptedStuff'); cryptoStub.encrypt.yields(null, 'encryptedStuff');
privkeyDaoStub.verifyAuthentication.yields(); privkeyDaoStub.verifyAuthentication.yields();
@ -1031,7 +1031,7 @@ define(function(require) {
publicKey: 'publicKey' publicKey: 'publicKey'
}); });
pgpStub.decrypt.yields(null, 'decryptedStuff'); pgpStub.decrypt.yields(null, 'decryptedStuff', true, true);
getDeviceSecretStub.yields(null, 'deviceSecret'); getDeviceSecretStub.yields(null, 'deviceSecret');
cryptoStub.encrypt.yields(null, 'encryptedStuff'); cryptoStub.encrypt.yields(null, 'encryptedStuff');
privkeyDaoStub.verifyAuthentication.yields(); privkeyDaoStub.verifyAuthentication.yields();