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:
commit
0cd4430103
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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!
|
||||||
|
@ -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
@ -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;
|
||||||
|
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user