1
0
mirror of https://github.com/moparisthebest/mail synced 2024-08-13 16:43:47 -04:00

[WO-279] Display error message if PGP signature is invalid

This commit is contained in:
Tankred Hase 2014-07-01 23:28:44 +02:00
parent f20dbede2c
commit bf063b5dac
7 changed files with 110 additions and 45 deletions

View File

@ -254,44 +254,60 @@ define(function(require) {
* Encrypt and sign a pgp message for a list of receivers
*/
PGP.prototype.encrypt = function(plaintext, publicKeysArmored, callback) {
var publicKeys = [];
var publicKeys;
// check keys
if (!this._privateKey || publicKeysArmored.length < 1) {
if (!this._privateKey) {
callback(new Error('Error encrypting. Keys must be set!'));
return;
}
// parse armored public keys
try {
publicKeysArmored.forEach(function(pubkeyArmored) {
publicKeys = publicKeys.concat(openpgp.key.readArmored(pubkeyArmored).keys);
});
if (publicKeysArmored && publicKeysArmored.length) {
publicKeys = [];
publicKeysArmored.forEach(function(pubkeyArmored) {
publicKeys = publicKeys.concat(openpgp.key.readArmored(pubkeyArmored).keys);
});
}
} catch (err) {
callback(new Error('Error encrypting plaintext!'));
return;
}
// encrypt and sign the plaintext
openpgp.signAndEncryptMessage(publicKeys, this._privateKey, plaintext, callback);
if (publicKeys) {
// encrypt and sign the plaintext
openpgp.signAndEncryptMessage(publicKeys, this._privateKey, plaintext, callback);
} else {
// if no public keys are available encrypt for myself
openpgp.signAndEncryptMessage([this._publicKey], this._privateKey, plaintext, callback);
}
};
/**
* Decrypt and verify a pgp message for a single sender.
* You need to check if signatures are both present and valid in the callback!
* [decrypt description]
* @param {String} ciphertext The encrypted PGP message block
* @param {String} publicKeyArmored The public key used to sign the message
* @param {Function} callback(error, plaintext, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed.
*/
PGP.prototype.decrypt = function(ciphertext, publicKeyArmored, callback) {
var publicKeys, message, signaturesValid;
// check keys
if (!this._privateKey || !publicKeyArmored) {
if (!this._privateKey) {
callback(new Error('Error decrypting. Keys must be set!'));
return;
}
// read keys and ciphertext message
try {
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
if (publicKeyArmored) {
// parse public keys if available ...
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
}
message = openpgp.message.readArmored(ciphertext);
} catch (err) {
callback(new Error('Error parsing encrypted PGP message!'));
@ -303,19 +319,19 @@ define(function(require) {
function onDecrypted(err, decrypted) {
if (err) {
callback(new Error('Error decrypting PGP message!'));
callback(new Error('Error decrypting and verifying PGP message!'));
return;
}
// check if signatures are valid
if (decrypted.signatures.length > 0) {
signaturesValid = true;
signaturesValid = true; // signature is correct
for (var i = 0; i < decrypted.signatures.length; i++) {
if (decrypted.signatures[i].valid === false) {
signaturesValid = false; // signature is wrong ... message was tampered with
break;
} else if (decrypted.signatures[i].valid === null) {
signaturesValid = undefined; // signature not found for the specified public key
signaturesValid = null; // signature not found for the specified public key
break;
}
}

View File

@ -831,19 +831,19 @@ define(function(require) {
return;
}
if (!senderPublicKey) {
// this should only happen if a mail from another channel is in the inbox
showError('Public key for sender not found!');
return;
}
// get the receiver's public key to check the message signature
var encryptedNode = filterBodyParts(message.bodyParts, 'encrypted')[0];
self._pgp.decrypt(encryptedNode.content, senderPublicKey.publicKey, function(err, decrypted, signaturesValid) {
var senderKey = senderPublicKey ? senderPublicKey.publicKey : undefined;
self._pgp.decrypt(encryptedNode.content, senderKey, function(err, decrypted, signaturesValid) {
if (err || !decrypted) {
return showError(err.message || 'An error occurred during the decryption.');
}
// if the decryption worked and signatures are present, everything's fine.
// no error is thrown if signatures are not present
message.signed = typeof signaturesValid !== 'undefined';
message.signaturesValid = signaturesValid;
// 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) {
@ -875,9 +875,6 @@ define(function(require) {
});
inlineExternalImages(message);
// if the decryption worked and signatures are present, everything's fine.
// no error is thrown if signatures are not present
message.signed = signaturesValid;
message.decrypted = true;
// we're done here!

View File

@ -1503,7 +1503,7 @@ module.exports = {
show_version: true,
show_comment: true,
versionstring: "OpenPGP.js v0.6.5",
versionstring: "OpenPGP.js v0.7.0",
commentstring: "http://openpgpjs.org",
keyserver: "keyserver.linux.it", // "pgp.mit.edu:11371"
@ -12439,24 +12439,25 @@ Message.prototype.verify = function(keys) {
var literalDataList = msg.packets.filterByTag(enums.packet.literal);
if (literalDataList.length !== 1) throw new Error('Can only verify message with one literal data packet.');
var signatureList = msg.packets.filterByTag(enums.packet.signature);
keys.forEach(function(key) {
for (var i = 0; i < signatureList.length; i++) {
var keyPacket = null;
for (var i = 0; i < signatureList.length; i++) {
keyPacket = key.getKeyPacket([signatureList[i].issuerKeyId]);
for (var j = 0; j < keys.length; j++) {
keyPacket = keys[j].getKeyPacket([signatureList[i].issuerKeyId]);
if (keyPacket) {
break;
}
}
var verifiedSig = {};
if (keyPacket) {
verifiedSig.keyid = signatureList[i].issuerKeyId;
verifiedSig.valid = signatureList[i].verify(keyPacket, literalDataList[0]);
} else {
verifiedSig.keyid = key.primaryKey.keyid;
verifiedSig.keyid = signatureList[i].issuerKeyId;
verifiedSig.valid = null;
}
result.push(verifiedSig);
});
}
return result;
};

View File

@ -117,6 +117,17 @@
}
}
.signature-status {
flex-shrink: 0;
padding: 0.9em;
text-align: center;
p {
color: $label-primary-back-color;
margin: 0;
}
}
.display-images {
flex-shrink: 0;
padding: 0.9em;

View File

@ -53,6 +53,11 @@
</div><!--/.working-->
</div><!--/.working-wrapper-->
<div class="signature-status"
ng-show="state.mailList.selected.signed && !state.mailList.selected.signaturesValid">
<p>Invalid PGP signature. This message could have been tampered with.</p>
</div>
<div class="display-images">
<a ng-show="html && showImageButton" href='#' wo-touch="displayImages(); $event.preventDefault()">Display images</a>
</div>

View File

@ -461,7 +461,8 @@ define(function(require) {
}, function(err) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.true;
expect(message.signed).to.be.undefined;
expect(message.signed).to.be.false;
expect(message.signaturesValid).to.be.undefined;
expect(message.attachments.length).to.equal(1);
expect(message.body).to.equal('test16');
done();
@ -484,6 +485,7 @@ define(function(require) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.true;
expect(message.signed).to.be.true;
expect(message.signaturesValid).to.be.true;
expect(message.attachments.length).to.equal(1);
expect(message.body).to.equal('test15');
done();
@ -506,6 +508,7 @@ define(function(require) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.true;
expect(message.signed).to.be.true;
expect(message.signaturesValid).to.be.true;
expect(message.attachments.length).to.equal(0);
expect(message.body).to.equal('test12');
done();
@ -527,7 +530,8 @@ define(function(require) {
}, function(err) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.true;
expect(message.signed).to.be.undefined;
expect(message.signed).to.be.false;
expect(message.signaturesValid).to.be.undefined;
expect(message.attachments.length).to.equal(0);
expect(message.body).to.equal('test13');
done();
@ -550,6 +554,7 @@ define(function(require) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.false;
expect(message.signed).to.be.true;
//TODO (check plaintext signatures): expect(message.signaturesValid).to.be.true;
expect(message.attachments.length).to.equal(1);
expect(message.body).to.equal('test17\n');
done();
@ -572,6 +577,7 @@ define(function(require) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.false;
expect(message.signed).to.be.true;
//TODO (check plaintext signatures): expect(message.signaturesValid).to.be.true;
expect(message.attachments.length).to.equal(0);
expect(message.body).to.equal('test14');
done();
@ -593,7 +599,8 @@ define(function(require) {
}, function(err) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.true;
expect(message.signed).to.be.undefined;
expect(message.signed).to.be.false;
expect(message.signaturesValid).to.be.undefined;
expect(message.attachments.length).to.equal(1);
expect(message.body).to.equal('test10');
done();
@ -602,7 +609,7 @@ define(function(require) {
};
});
it.skip('should parse Thunderbird (attachment - PGP/MIME): Encrypted and signed', function(done) {
it('should parse Thunderbird (attachment - PGP/MIME): Encrypted and signed', function(done) {
emailDao.onIncomingMessage = function(messages) {
emailDao.getBody({
folder: currentFolder,
@ -616,6 +623,7 @@ define(function(require) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.true;
expect(message.signed).to.be.true;
expect(message.signaturesValid).to.be.true;
expect(message.attachments.length).to.equal(1);
expect(message.body).to.equal('test9');
done();
@ -624,7 +632,7 @@ define(function(require) {
};
});
it.skip('should parse Thunderbird (no attachment): Encrypted and signed', function(done) {
it('should parse Thunderbird (no attachment): Encrypted and signed', function(done) {
emailDao.onIncomingMessage = function(messages) {
emailDao.getBody({
folder: currentFolder,
@ -638,6 +646,7 @@ define(function(require) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.true;
expect(message.signed).to.be.true;
expect(message.signaturesValid).to.be.true;
expect(message.attachments.length).to.equal(0);
expect(message.body).to.equal('test4\n');
done();
@ -660,6 +669,7 @@ define(function(require) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.true;
expect(message.signed).to.be.false;
expect(message.signaturesValid).to.be.undefined;
expect(message.attachments.length).to.equal(0);
expect(message.body).to.equal('test5\n');
done();
@ -682,6 +692,7 @@ define(function(require) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.false;
expect(message.signed).to.be.false;
expect(message.signaturesValid).to.be.undefined;
expect(message.attachments.length).to.equal(0);
expect(message.body).to.equal('test8\n\n23.06.14 21:12, safewithme kirjutas:\n> test8');
done();
@ -704,6 +715,7 @@ define(function(require) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.false;
expect(message.signed).to.be.true;
//TODO (check plaintext signatures): expect(message.signaturesValid).to.be.true;
expect(message.attachments.length).to.equal(1);
expect(message.body).to.equal('test11');
done();
@ -726,6 +738,7 @@ define(function(require) {
expect(err).to.not.exist;
expect(message.encrypted).to.be.false;
expect(message.signed).to.be.true;
//TODO (check plaintext signatures): expect(message.signaturesValid).to.be.true;
expect(message.attachments.length).to.equal(0);
expect(message.body).to.equal('test6');
done();

View File

@ -2,6 +2,7 @@ define(function(require) {
'use strict';
var PGP = require('js/crypto/pgp'),
openpgp = require('openpgp'),
expect = chai.expect;
describe('PGP Crypto Api unit tests', function() {
@ -13,7 +14,7 @@ define(function(require) {
keySize = 512,
keyId = 'F6F60E9B42CDFF4C',
pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n' +
'Version: OpenPGP.js v0.6.5\r\n' +
'Version: OpenPGP.js v0.7.0\r\n' +
'Comment: http://openpgpjs.org\r\n' +
'\r\n' +
'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5\r\n' +
@ -24,7 +25,7 @@ define(function(require) {
'=6XMW\r\n' +
'-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n',
privkey = '-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n' +
'Version: OpenPGP.js v0.6.5\r\n' +
'Version: OpenPGP.js v0.7.0\r\n' +
'Comment: http://openpgpjs.org\r\n' +
'\r\n' +
'xcBeBFJYTLwBAf9jGbQlDgGL8ixYw6dzgTBp9xL/BcI88j2yBdCVMPi+8tl0\r\n' +
@ -219,7 +220,6 @@ define(function(require) {
var keyId = pgp.getKeyId();
expect(keyId).to.equal('F6F60E9B42CDFF4C');
});
it('should work with param', function() {
var keyId = pgp.getKeyId(pubkey);
expect(keyId).to.equal('F6F60E9B42CDFF4C');
@ -231,7 +231,6 @@ define(function(require) {
var fingerprint = pgp.getFingerprint();
expect(fingerprint).to.equal('5856CEF789C3A307E8A1B976F6F60E9B42CDFF4C');
});
it('should work with param', function() {
var fingerprint = pgp.getFingerprint(pubkey);
expect(fingerprint).to.equal('5856CEF789C3A307E8A1B976F6F60E9B42CDFF4C');
@ -272,7 +271,6 @@ define(function(require) {
done();
});
});
it('should work', function(done) {
pgp.encrypt(message, [pubkey], function(err, ct) {
expect(err).to.not.exist;
@ -280,10 +278,18 @@ define(function(require) {
done();
});
});
it('should encrypt to myself if public keys are empty', function(done) {
pgp.encrypt(message, undefined, function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
done();
});
});
});
describe('Decrypt and verify', function() {
var ciphertext;
var wrongPubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: OpenPGP.js v.1.20131116\r\nComment: Whiteout Mail - http://whiteout.io\r\n\r\nxsBNBFKODs4BB/9iOF4THsjQMY+WEpT7ShgKxj4bHzRRaQkqczS4nZvP0U3g\r\nqeqCnbpagyeKXA+bhWFQW4GmXtgAoeD5PXs6AZYrw3tWNxLKu2Oe6Tp9K/XI\r\nxTMQ2wl4qZKDXHvuPsJ7cmgaWqpPyXtxA4zHHS3WrkI/6VzHAcI/y6x4szSB\r\nKgSuhI3hjh3s7TybUC1U6AfoQGx/S7e3WwlCOrK8GTClirN/2mCPRC5wuIft\r\nnkoMfA6jK8d2OPrJ63shy5cgwHOjQg/xuk46dNS7tkvGmbaa+X0PgqSKB+Hf\r\nYPPNS/ylg911DH9qa8BqYU2QpNh9jUKXSF+HbaOM+plWkCSAL7czV+R3ABEB\r\nAAHNLVdoaXRlb3V0IFVzZXIgPHNhZmV3aXRobWUudGVzdHVzZXJAZ21haWwu\r\nY29tPsLAXAQQAQgAEAUCUo4O2gkQ1/uT/N+/wjwAAN2cB/9gFRmAfvEQ2qz+\r\nWubmT2EsSSnjPMxzG4uyykFoa+TaZCWo2Xa2tQghmU103kEkQb1OEjRjpgwJ\r\nYX9Kghnl8DByM686L5AXnRyHP78qRJCLXSXl0AGicboUDp5sovaa4rswQceH\r\nvcdWgZ/mgHTRoiQeJddy9k+H6MPFiyFaVcFwegVsmpc+dCcC8yT+qh8ZIbyG\r\nRJU60PmKKN7LUusP+8DbSv39zCGJCBlVVKyA4MzdF5uM+sqTdXbKzOrT5DGd\r\nCZaox4s+w16Sq1rHzZKFWfQPfKLDB9pyA0ufCVRA3AF6BUi7G3ZqhZiHNhMP\r\nNvE45V/hS1PbZcfPVoUjE2qc1Ix1\r\n=7Wpe\r\n-----END PGP PUBLIC KEY BLOCK-----';
beforeEach(function(done) {
pgp.encrypt(message, [pubkey], function(err, ct) {
@ -303,7 +309,6 @@ define(function(require) {
done();
});
});
it('should work', function(done) {
pgp.decrypt(ciphertext, pubkey, function(err, pt, signValid) {
expect(err).to.not.exist;
@ -312,17 +317,34 @@ define(function(require) {
done();
});
});
it('should work without signature', function(done) {
var ct = openpgp.encryptMessage([pgp._publicKey], message);
it('should decrypt but signValid should be undefined for wrong public key', function(done) {
var wrongPubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: OpenPGP.js v.1.20131116\r\nComment: Whiteout Mail - http://whiteout.io\r\n\r\nxsBNBFKODs4BB/9iOF4THsjQMY+WEpT7ShgKxj4bHzRRaQkqczS4nZvP0U3g\r\nqeqCnbpagyeKXA+bhWFQW4GmXtgAoeD5PXs6AZYrw3tWNxLKu2Oe6Tp9K/XI\r\nxTMQ2wl4qZKDXHvuPsJ7cmgaWqpPyXtxA4zHHS3WrkI/6VzHAcI/y6x4szSB\r\nKgSuhI3hjh3s7TybUC1U6AfoQGx/S7e3WwlCOrK8GTClirN/2mCPRC5wuIft\r\nnkoMfA6jK8d2OPrJ63shy5cgwHOjQg/xuk46dNS7tkvGmbaa+X0PgqSKB+Hf\r\nYPPNS/ylg911DH9qa8BqYU2QpNh9jUKXSF+HbaOM+plWkCSAL7czV+R3ABEB\r\nAAHNLVdoaXRlb3V0IFVzZXIgPHNhZmV3aXRobWUudGVzdHVzZXJAZ21haWwu\r\nY29tPsLAXAQQAQgAEAUCUo4O2gkQ1/uT/N+/wjwAAN2cB/9gFRmAfvEQ2qz+\r\nWubmT2EsSSnjPMxzG4uyykFoa+TaZCWo2Xa2tQghmU103kEkQb1OEjRjpgwJ\r\nYX9Kghnl8DByM686L5AXnRyHP78qRJCLXSXl0AGicboUDp5sovaa4rswQceH\r\nvcdWgZ/mgHTRoiQeJddy9k+H6MPFiyFaVcFwegVsmpc+dCcC8yT+qh8ZIbyG\r\nRJU60PmKKN7LUusP+8DbSv39zCGJCBlVVKyA4MzdF5uM+sqTdXbKzOrT5DGd\r\nCZaox4s+w16Sq1rHzZKFWfQPfKLDB9pyA0ufCVRA3AF6BUi7G3ZqhZiHNhMP\r\nNvE45V/hS1PbZcfPVoUjE2qc1Ix1\r\n=7Wpe\r\n-----END PGP PUBLIC KEY BLOCK-----';
pgp.decrypt(ciphertext, wrongPubkey, function(err, pt, signValid) {
pgp.decrypt(ct, undefined, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.undefined;
done();
});
});
it('should fail to verify if public keys are empty', function(done) {
// setup another public key so that signature verification fails
pgp._publicKey = openpgp.key.readArmored(wrongPubkey).keys[0];
pgp.decrypt(ciphertext, undefined, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.null;
done();
});
});
it('should decrypt but signValid should be null for wrong public key', function(done) {
pgp.decrypt(ciphertext, wrongPubkey, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.null;
done();
});
});
});
});