diff --git a/src/js/crypto/pgp.js b/src/js/crypto/pgp.js
index c006e06..f3ae0bb 100644
--- a/src/js/crypto/pgp.js
+++ b/src/js/crypto/pgp.js
@@ -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;
}
}
diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js
index 01814c4..4e63b89 100644
--- a/src/js/dao/email-dao.js
+++ b/src/js/dao/email-dao.js
@@ -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!
diff --git a/src/lib/openpgp/openpgp.js b/src/lib/openpgp/openpgp.js
index 9ed83d7..6de4190 100644
--- a/src/lib/openpgp/openpgp.js
+++ b/src/lib/openpgp/openpgp.js
@@ -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;
};
diff --git a/src/sass/views/_read.scss b/src/sass/views/_read.scss
index 5ee37f2..b3c58c3 100644
--- a/src/sass/views/_read.scss
+++ b/src/sass/views/_read.scss
@@ -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;
diff --git a/src/tpl/read.html b/src/tpl/read.html
index 98647d3..3224c46 100644
--- a/src/tpl/read.html
+++ b/src/tpl/read.html
@@ -53,6 +53,11 @@
+
+
Invalid PGP signature. This message could have been tampered with.
+
+
diff --git a/test/integration/email-dao-test.js b/test/integration/email-dao-test.js
index f49bc52..0a1a0d4 100644
--- a/test/integration/email-dao-test.js
+++ b/test/integration/email-dao-test.js
@@ -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();
diff --git a/test/unit/pgp-test.js b/test/unit/pgp-test.js
index 0c0a5f5..3964307 100644
--- a/test/unit/pgp-test.js
+++ b/test/unit/pgp-test.js
@@ -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();
+ });
+ });
});
});