From 5140a946e5e77fbd058d08b7ccb04aa37f839912 Mon Sep 17 00:00:00 2001 From: Aydar Zartdinov Date: Fri, 10 Feb 2017 12:12:05 +0300 Subject: [PATCH 1/6] Added ability to sign and verify public keys --- src/key.js | 84 ++++++++++++++++++++++++++++++++++++++- src/openpgp.js | 68 ++++++++++++++++++++++++++++++- test/general/openpgp.js | 68 +++++++++++++++++++++++++------ test/general/signature.js | 37 +++++++++++++++++ 4 files changed, 241 insertions(+), 16 deletions(-) diff --git a/src/key.js b/src/key.js index 9bcc1e68..afbe435a 100644 --- a/src/key.js +++ b/src/key.js @@ -507,11 +507,11 @@ function getExpirationTime(keyPacket, selfCertificate) { Key.prototype.getPrimaryUser = function() { var primUser = []; for (var i = 0; i < this.users.length; i++) { - if (!this.users[i].userId || !this.users[i].selfCertifications) { + if ((!this.users[i].userId && !this.users[i].userAttribute) || !this.users[i].selfCertifications) { continue; } for (var j = 0; j < this.users[i].selfCertifications.length; j++) { - primUser.push({user: this.users[i], selfCertificate: this.users[i].selfCertifications[j]}); + primUser.push({index: i, user: this.users[i], selfCertificate: this.users[i].selfCertifications[j]}); } } // sort by primary user flag and signature creation time @@ -638,6 +638,86 @@ Key.prototype.revoke = function() { }; +/** + * Signs the public key + * @param {Array} privateKey decrypted private keys for signing + * @return {module:key~Key} new public key with new certificate signature + */ +Key.prototype.sign = function(privateKeys) { + var primaryUser, primaryKey, dataToSign, signingKeyPacket, signaturePacket, user, key; + if (this.isPrivate()) { + throw new Error('Only public key can be signed'); + } + primaryUser = this.getPrimaryUser(); + if (!primaryUser) { + throw new Error('Could not find primary user'); + } + primaryKey = this.primaryKey; + dataToSign = {}; + dataToSign.userid = primaryUser.user.userId || primaryUser.user.userAttribute; + dataToSign.key = primaryKey; + user = new User(primaryUser.user.userId || primaryUser.user.userAttribute); + user.otherCertifications = []; + privateKeys.forEach(function(privateKey) { + if (privateKey.isPublic()) { + throw new Error('Need private key for signing'); + } + if (privateKey.primaryKey.getKeyId().equals(primaryKey.getKeyId())) { + throw new Error('No need to self signinig'); + } + signingKeyPacket = privateKey.getSigningKeyPacket(); + if (!signingKeyPacket) { + throw new Error('Could not find valid signing key packet'); + } + if (!signingKeyPacket.isDecrypted) { + throw new Error('Private key is not decrypted.'); + } + signaturePacket = new packet.Signature(); + // Most OpenPGP implementations use generic certification (0x10) + signaturePacket.signatureType = enums.write(enums.signature, enums.signature.cert_generic); + signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; + signaturePacket.hashAlgorithm = privateKey.getPreferredHashAlgorithm(); + signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; + signaturePacket.signingKeyId = signingKeyPacket.getKeyId(); + signaturePacket.sign(signingKeyPacket, dataToSign); + user.otherCertifications.push(signaturePacket); + }); + user.update(primaryUser.user, this.primaryKey); + key = new Key(this.toPacketlist()); + key.users[primaryUser.index] = user; + return key; +}; + +/** + * Verifies public key + * @param {Array} keys array of keys to verify certificate signatures + * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature + */ +Key.prototype.verify = function(keys) { + var primaryKey, primaryUser, user; + if (this.isPrivate()) { + throw new Error('Only public key can be verified'); + } + primaryKey = this.primaryKey; + primaryUser = this.getPrimaryUser(); + if (!primaryUser) { + throw new Error('Could not find primary user'); + } + user = primaryUser.user; + if (!user.otherCertifications) { + return []; + } + return user.otherCertifications.map(function(signaturePacket) { + var keyPacket = keys.find(function(key) { + return key.getSigningKeyPacket(signaturePacket.issuerKeyId); + }) || null; + return { + keyid: signaturePacket.issuerKeyId, + valid: keyPacket && signaturePacket.verify(keyPacket.primaryKey, {userid: user.userId || user.userAttribute, key: primaryKey}) + }; + }); +}; + /** * @class * @classdesc Class that represents an user ID or attribute packet and the relevant signatures. diff --git a/src/openpgp.js b/src/openpgp.js index b027991e..fd0d5b68 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -34,7 +34,7 @@ import * as messageLib from './message.js'; import * as cleartext from './cleartext.js'; -import * as key from './key.js'; +import * as keyLib from './key.js'; import config from './config/config.js'; import util from './util'; import AsyncProxy from './worker/async_proxy.js'; @@ -104,7 +104,7 @@ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=fal return asyncProxy.delegate('generateKey', options); } - return key.generate(options).then(newKey => ({ + return keyLib.generate(options).then(newKey => ({ key: newKey, privateKeyArmored: newKey.armor(), @@ -361,6 +361,65 @@ export function decryptSessionKey({ message, privateKey, password }) { } +///////////////////////////////////////////// +// // +// Public key signing and verification // +// // +///////////////////////////////////////////// + + +/** + * Signs a paublic key. + * @param {Key} publicKey public key to be signed + * @param {Key|Array} privateKeys array of keys or single key with decrypted secret key data to sign public key + * @return {Promise} Public key object in form: + * { publicKey:Key, publicKeyArmored:String } + * @static + */ +export function signPublicKey({ publicKey, privateKeys }) { + checkKey(publicKey, 'publicKey'); + privateKeys = toArray(privateKeys); + + if (asyncProxy) { // use web worker if available + return asyncProxy.delegate('signPublicKey', { publicKey, privateKeys }); + } + + return execute(() => { + + const signedPublicKey = publicKey.sign(privateKeys); + + return { + publicKey: signedPublicKey, + publicKeyArmored: signedPublicKey.armor() + }; + + }, 'Error signing public key'); +} + +/** + * Verifies public key + * @param {Key} publicKey public key object with signatures + * @param {Key|Array} publicKeys array of publicKeys or single key, to verify signatures + * @return {Promise} cleartext with status of verified signatures in the form of: + * { signatures: [{ keyid:String, valid:Boolean|null }] } + * @static + */ +export function verifyPublicKey({ publicKey, publicKeys }) { + checkKey(publicKey, 'publicKey'); + publicKeys = toArray(publicKeys); + + if (asyncProxy) { // use web worker if available + return asyncProxy.delegate('verifyPublicKey', { publicKey, publicKeys }); + } + + return execute(() => ({ + + signatures: publicKey.verify(publicKeys) + + }), 'Error verifying signed public key'); +} + + ////////////////////////// // // // Helper functions // @@ -396,6 +455,11 @@ function checkCleartextMessage(message) { throw new Error('Parameter [message] needs to be of type CleartextMessage'); } } +function checkKey(key, name) { + if (!keyLib.Key.prototype.isPrototypeOf(key)) { + throw new Error('Parameter [' + (name || 'key') + '] needs to be of type Key'); + } +} /** * Format user ids for internal use. diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 9cde0211..ebab63e7 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -158,6 +158,20 @@ var priv_key_de = '=kyeP', '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); + +var wrong_pubkey = [ + '-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: OpenPGP.js v0.9.0', + 'Comment: Hoodiecrow - https://hoodiecrow.com', + '', + 'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5', + 'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg', + 'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM', + 'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq', + 'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1', + '=6XMW', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + var passphrase = 'hello world'; var plaintext = 'short message\nnext line\n한국어/조선말'; var password1 = 'I am a password'; @@ -607,18 +621,6 @@ describe('OpenPGP.js public api tests', function() { }); describe('AES / RSA encrypt, decrypt, sign, verify', function() { - var wrong_pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n' + - 'Version: OpenPGP.js v0.9.0\r\n' + - 'Comment: Hoodiecrow - https://hoodiecrow.com\r\n' + - '\r\n' + - 'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5\r\n' + - 'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg\r\n' + - 'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM\r\n' + - 'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq\r\n' + - 'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1\r\n' + - '=6XMW\r\n' + - '-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n'; - beforeEach(function() { expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; }); @@ -909,6 +911,48 @@ describe('OpenPGP.js public api tests', function() { }); }); }); + + describe('signPublicKey, verifyPublicKey', function() { + beforeEach(function() { + expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; + }); + + it('should sign and verify public key', function(done) { + var signOpt = { + publicKey: openpgp.key.readArmored(pub_key_de).keys[0], + privateKeys: privateKey.keys + }; + var verifyOpt = { + publicKeys: publicKey.keys + }; + openpgp.signPublicKey(signOpt).then(function(signed) { + verifyOpt.publicKey = signed.publicKey; + return openpgp.verifyPublicKey(verifyOpt); + }).then(function(verified) { + expect(verified.signatures[0].valid).to.be.true; + expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + done(); + }); + }); + + it('should sign and fail to verify public key with wrong public key', function(done) { + var signOpt = { + publicKey: openpgp.key.readArmored(pub_key_de).keys[0], + privateKeys: privateKey.keys + }; + var verifyOpt = { + publicKeys: openpgp.key.readArmored(wrong_pubkey).keys + }; + openpgp.signPublicKey(signOpt).then(function(signed) { + verifyOpt.publicKey = signed.publicKey; + return openpgp.verifyPublicKey(verifyOpt); + }).then(function(verified) { + expect(verified.signatures[0].valid).to.be.null; + expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + done(); + }); + }); + }); } }); diff --git a/test/general/signature.js b/test/general/signature.js index 7370380e..81eb6343 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -654,5 +654,42 @@ describe("Signature", function() { done(); }); }); + + it('Verify signed public key', function(done) { + var signedArmor = [ + '-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v1', + '', + 'mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+', + 'fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5', + 'GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0', + 'JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS', + 'YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6', + 'AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki', + 'Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf', + '9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rCIRgQQEQIABgUCVuXBfQAK', + 'CRARJ5QDyxae+O0fAJ9hUQPejXvZv6VW1Q3/Pm3+x2wfJACgwFg9NlrPPfejoC1w', + 'P+z+vE5NFA24jQRSYS9OAQQA6R/PtBFaJaT4jq10yqASk4sqwVMsc6HcifM5lSdx', + 'zExFP74naUMMyEsKHP53QxTF0GrqusagQg/ZtgT0CN1HUM152y7ACOdp1giKjpMz', + 'OTQClqCoclyvWOFB+L/SwGEIJf7LSCErwoBuJifJc8xAVr0XX0JthoW+uP91eTQ3', + 'XpsAEQEAAYkBPQQYAQIACQUCUmEvTgIbLgCoCRBKY2E6TW5AlJ0gBBkBAgAGBQJS', + 'YS9OAAoJEOCE90RsICyXuqIEANmmiRCASF7YK7PvFkieJNwzeK0V3F2lGX+uu6Y3', + 'Q/Zxdtwc4xR+me/CSBmsURyXTO29OWhPGLszPH9zSJU9BdDi6v0yNprmFPX/1Ng0', + 'Abn/sCkwetvjxC1YIvTLFwtUL/7v6NS2bZpsUxRTg9+cSrMWWSNjiY9qUKajm1tu', + 'zPDZXAUEAMNmAN3xXN/Kjyvj2OK2ck0XW748sl/tc3qiKPMJ+0AkMF7Pjhmh9nxq', + 'E9+QCEl7qinFqqBLjuzgUhBU4QlwX1GDAtNTq6ihLMD5v1d82ZC7tNatdlDMGWnI', + 'dvEMCv2GZcuIqDQ9rXWs49e7tq1NncLYhz3tYjKhoFTKEIq3y3Pp', + '=fvK7', + '-----END PGP PUBLIC KEY BLOCK-----' + ].join('\n'); + + var sig_key = openpgp.key.readArmored(signedArmor).keys[0]; + var pub_key = openpgp.key.readArmored(priv_key_arm1).keys[0].toPublic(); + openpgp.verifyPublicKey({ publicKey: sig_key, publicKeys: [pub_key] }).then(function(verified) { + expect(verified.signatures[0].valid).to.be.true; + expect(verified.signatures[0].keyid.toHex()).to.equal(pub_key.primaryKey.getKeyId().toHex()); + done(); + }); + }); }); From 715f98bb38dbaac6b53d07d861e26b422f5e7268 Mon Sep 17 00:00:00 2001 From: Aydar Zartdinov Date: Fri, 10 Feb 2017 12:12:29 +0300 Subject: [PATCH 2/6] Reverted top level api changes --- src/key.js | 2 +- src/openpgp.js | 68 ++--------------------------------------- test/general/openpgp.js | 68 ++++++++--------------------------------- 3 files changed, 15 insertions(+), 123 deletions(-) diff --git a/src/key.js b/src/key.js index afbe435a..ad119318 100644 --- a/src/key.js +++ b/src/key.js @@ -507,7 +507,7 @@ function getExpirationTime(keyPacket, selfCertificate) { Key.prototype.getPrimaryUser = function() { var primUser = []; for (var i = 0; i < this.users.length; i++) { - if ((!this.users[i].userId && !this.users[i].userAttribute) || !this.users[i].selfCertifications) { + if (!this.users[i].userId || !this.users[i].selfCertifications) { continue; } for (var j = 0; j < this.users[i].selfCertifications.length; j++) { diff --git a/src/openpgp.js b/src/openpgp.js index fd0d5b68..b027991e 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -34,7 +34,7 @@ import * as messageLib from './message.js'; import * as cleartext from './cleartext.js'; -import * as keyLib from './key.js'; +import * as key from './key.js'; import config from './config/config.js'; import util from './util'; import AsyncProxy from './worker/async_proxy.js'; @@ -104,7 +104,7 @@ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=fal return asyncProxy.delegate('generateKey', options); } - return keyLib.generate(options).then(newKey => ({ + return key.generate(options).then(newKey => ({ key: newKey, privateKeyArmored: newKey.armor(), @@ -361,65 +361,6 @@ export function decryptSessionKey({ message, privateKey, password }) { } -///////////////////////////////////////////// -// // -// Public key signing and verification // -// // -///////////////////////////////////////////// - - -/** - * Signs a paublic key. - * @param {Key} publicKey public key to be signed - * @param {Key|Array} privateKeys array of keys or single key with decrypted secret key data to sign public key - * @return {Promise} Public key object in form: - * { publicKey:Key, publicKeyArmored:String } - * @static - */ -export function signPublicKey({ publicKey, privateKeys }) { - checkKey(publicKey, 'publicKey'); - privateKeys = toArray(privateKeys); - - if (asyncProxy) { // use web worker if available - return asyncProxy.delegate('signPublicKey', { publicKey, privateKeys }); - } - - return execute(() => { - - const signedPublicKey = publicKey.sign(privateKeys); - - return { - publicKey: signedPublicKey, - publicKeyArmored: signedPublicKey.armor() - }; - - }, 'Error signing public key'); -} - -/** - * Verifies public key - * @param {Key} publicKey public key object with signatures - * @param {Key|Array} publicKeys array of publicKeys or single key, to verify signatures - * @return {Promise} cleartext with status of verified signatures in the form of: - * { signatures: [{ keyid:String, valid:Boolean|null }] } - * @static - */ -export function verifyPublicKey({ publicKey, publicKeys }) { - checkKey(publicKey, 'publicKey'); - publicKeys = toArray(publicKeys); - - if (asyncProxy) { // use web worker if available - return asyncProxy.delegate('verifyPublicKey', { publicKey, publicKeys }); - } - - return execute(() => ({ - - signatures: publicKey.verify(publicKeys) - - }), 'Error verifying signed public key'); -} - - ////////////////////////// // // // Helper functions // @@ -455,11 +396,6 @@ function checkCleartextMessage(message) { throw new Error('Parameter [message] needs to be of type CleartextMessage'); } } -function checkKey(key, name) { - if (!keyLib.Key.prototype.isPrototypeOf(key)) { - throw new Error('Parameter [' + (name || 'key') + '] needs to be of type Key'); - } -} /** * Format user ids for internal use. diff --git a/test/general/openpgp.js b/test/general/openpgp.js index ebab63e7..9cde0211 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -158,20 +158,6 @@ var priv_key_de = '=kyeP', '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); - -var wrong_pubkey = [ - '-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: OpenPGP.js v0.9.0', - 'Comment: Hoodiecrow - https://hoodiecrow.com', - '', - 'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5', - 'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg', - 'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM', - 'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq', - 'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1', - '=6XMW', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); - var passphrase = 'hello world'; var plaintext = 'short message\nnext line\n한국어/조선말'; var password1 = 'I am a password'; @@ -621,6 +607,18 @@ describe('OpenPGP.js public api tests', function() { }); describe('AES / RSA encrypt, decrypt, sign, verify', function() { + var wrong_pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n' + + 'Version: OpenPGP.js v0.9.0\r\n' + + 'Comment: Hoodiecrow - https://hoodiecrow.com\r\n' + + '\r\n' + + 'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5\r\n' + + 'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg\r\n' + + 'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM\r\n' + + 'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq\r\n' + + 'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1\r\n' + + '=6XMW\r\n' + + '-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n'; + beforeEach(function() { expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; }); @@ -911,48 +909,6 @@ describe('OpenPGP.js public api tests', function() { }); }); }); - - describe('signPublicKey, verifyPublicKey', function() { - beforeEach(function() { - expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; - }); - - it('should sign and verify public key', function(done) { - var signOpt = { - publicKey: openpgp.key.readArmored(pub_key_de).keys[0], - privateKeys: privateKey.keys - }; - var verifyOpt = { - publicKeys: publicKey.keys - }; - openpgp.signPublicKey(signOpt).then(function(signed) { - verifyOpt.publicKey = signed.publicKey; - return openpgp.verifyPublicKey(verifyOpt); - }).then(function(verified) { - expect(verified.signatures[0].valid).to.be.true; - expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); - done(); - }); - }); - - it('should sign and fail to verify public key with wrong public key', function(done) { - var signOpt = { - publicKey: openpgp.key.readArmored(pub_key_de).keys[0], - privateKeys: privateKey.keys - }; - var verifyOpt = { - publicKeys: openpgp.key.readArmored(wrong_pubkey).keys - }; - openpgp.signPublicKey(signOpt).then(function(signed) { - verifyOpt.publicKey = signed.publicKey; - return openpgp.verifyPublicKey(verifyOpt); - }).then(function(verified) { - expect(verified.signatures[0].valid).to.be.null; - expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); - done(); - }); - }); - }); } }); From 3fa4c0c760d781914306906356dc500bff391e79 Mon Sep 17 00:00:00 2001 From: Aydar Zartdinov Date: Fri, 10 Feb 2017 12:12:38 +0300 Subject: [PATCH 3/6] Base functionality moved to User.prototype --- src/key.js | 162 +++++++++++++++++++++++--------------- test/general/key.js | 129 +++++++++++++++++++++++++++++- test/general/signature.js | 21 ++--- 3 files changed, 237 insertions(+), 75 deletions(-) diff --git a/src/key.js b/src/key.js index ad119318..b91dbc2e 100644 --- a/src/key.js +++ b/src/key.js @@ -639,83 +639,61 @@ Key.prototype.revoke = function() { }; /** - * Signs the public key + * Signs primary user of key * @param {Array} privateKey decrypted private keys for signing * @return {module:key~Key} new public key with new certificate signature */ -Key.prototype.sign = function(privateKeys) { - var primaryUser, primaryKey, dataToSign, signingKeyPacket, signaturePacket, user, key; - if (this.isPrivate()) { - throw new Error('Only public key can be signed'); - } - primaryUser = this.getPrimaryUser(); - if (!primaryUser) { +Key.prototype.signPrimaryUser = function(privateKeys) { + var {index, user} = this.getPrimaryUser() || {}; + if (!user) { throw new Error('Could not find primary user'); } - primaryKey = this.primaryKey; - dataToSign = {}; - dataToSign.userid = primaryUser.user.userId || primaryUser.user.userAttribute; - dataToSign.key = primaryKey; - user = new User(primaryUser.user.userId || primaryUser.user.userAttribute); - user.otherCertifications = []; - privateKeys.forEach(function(privateKey) { - if (privateKey.isPublic()) { - throw new Error('Need private key for signing'); - } - if (privateKey.primaryKey.getKeyId().equals(primaryKey.getKeyId())) { - throw new Error('No need to self signinig'); - } - signingKeyPacket = privateKey.getSigningKeyPacket(); - if (!signingKeyPacket) { - throw new Error('Could not find valid signing key packet'); - } - if (!signingKeyPacket.isDecrypted) { - throw new Error('Private key is not decrypted.'); - } - signaturePacket = new packet.Signature(); - // Most OpenPGP implementations use generic certification (0x10) - signaturePacket.signatureType = enums.write(enums.signature, enums.signature.cert_generic); - signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; - signaturePacket.hashAlgorithm = privateKey.getPreferredHashAlgorithm(); - signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; - signaturePacket.signingKeyId = signingKeyPacket.getKeyId(); - signaturePacket.sign(signingKeyPacket, dataToSign); - user.otherCertifications.push(signaturePacket); - }); - user.update(primaryUser.user, this.primaryKey); - key = new Key(this.toPacketlist()); - key.users[primaryUser.index] = user; + user = user.sign(this.primaryKey, privateKeys); + var key = new Key(this.toPacketlist()); + key.users[index] = user; return key; }; /** - * Verifies public key + * Signs all users of key + * @param {Array} privateKeys decrypted private keys for signing + * @return {module:key~Key} new public key with new certificate signature + */ +Key.prototype.signAllUsers = function(privateKeys) { + var users = this.users.map(user => user.sign(this.primaryKey, privateKeys)); + var key = new Key(this.toPacketlist()); + key.users = users; + return key; +}; + +/** + * Verifies primary user of key * @param {Array} keys array of keys to verify certificate signatures * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -Key.prototype.verify = function(keys) { - var primaryKey, primaryUser, user; - if (this.isPrivate()) { - throw new Error('Only public key can be verified'); - } - primaryKey = this.primaryKey; - primaryUser = this.getPrimaryUser(); - if (!primaryUser) { +Key.prototype.verifyPrimaryUser = function(keys) { + var {user} = this.getPrimaryUser() || {}; + if (!user) { throw new Error('Could not find primary user'); } - user = primaryUser.user; - if (!user.otherCertifications) { - return []; - } - return user.otherCertifications.map(function(signaturePacket) { - var keyPacket = keys.find(function(key) { - return key.getSigningKeyPacket(signaturePacket.issuerKeyId); - }) || null; - return { - keyid: signaturePacket.issuerKeyId, - valid: keyPacket && signaturePacket.verify(keyPacket.primaryKey, {userid: user.userId || user.userAttribute, key: primaryKey}) - }; - }); + return user.verifyAllSignatures(this.primaryKey, keys); +}; + +/** + * Verifies all users of key + * @param {Array} keys array of keys to verify certificate signatures + * @return {Array<({userid: String, keyid: module:type/keyid, valid: Boolean})>} list of userid, signer's keyid and validity of signature + */ +Key.prototype.verifyAllUsers = function(keys) { + return this.users.reduce((signatures, user) => { + return signatures.concat( + user.verifyAllSignatures(this.primaryKey, keys).map(signature => ({ + userid: user.userId.userid, + keyid: signature.keyid, + valid: signature.valid + })) + ); + }, []); }; /** @@ -807,6 +785,64 @@ User.prototype.isValidSelfCertificate = function(primaryKey, selfCertificate) { return false; }; +/** + * Signs user + * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet + * @param {Array} privateKeys decrypted private keys for signing + * @return {module:key~Key} new user with new certificate signatures + */ +User.prototype.sign = function(primaryKey, privateKeys) { + var user, dataToSign, signingKeyPacket, signaturePacket; + dataToSign = {}; + dataToSign.key = primaryKey; + dataToSign.userid = this.userId || this.userAttribute; + user = new User(this.userId || this.userAttribute); + user.otherCertifications = []; + privateKeys.forEach(privateKey => { + if (privateKey.isPublic()) { + throw new Error('Need private key for signing'); + } + if (privateKey.primaryKey.getKeyId().equals(primaryKey.getKeyId())) { + throw new Error('Not implemented for self signing'); + } + signingKeyPacket = privateKey.getSigningKeyPacket(); + if (!signingKeyPacket) { + throw new Error('Could not find valid signing key packet'); + } + if (!signingKeyPacket.isDecrypted) { + throw new Error('Private key is not decrypted.'); + } + signaturePacket = new packet.Signature(); + // Most OpenPGP implementations use generic certification (0x10) + signaturePacket.signatureType = enums.write(enums.signature, enums.signature.cert_generic); + signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; + signaturePacket.hashAlgorithm = privateKey.getPreferredHashAlgorithm(); + signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; + signaturePacket.signingKeyId = signingKeyPacket.getKeyId(); + signaturePacket.sign(signingKeyPacket, dataToSign); + user.otherCertifications.push(signaturePacket); + }); + user.update(this, primaryKey); + return user; +}; + +/** + * Verifies all user signatures + * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet + * @param {Array} keys array of keys to verify certificate signatures + * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature + */ +User.prototype.verifyAllSignatures = function(primaryKey, keys) { + var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; + return this.selfCertifications.concat(this.otherCertifications).map(signaturePacket => { + var keyPacket = keys.find(key => key.getSigningKeyPacket(signaturePacket.issuerKeyId)) || null; + return { + keyid: signaturePacket.issuerKeyId, + valid: keyPacket && signaturePacket.verify(keyPacket.primaryKey, dataToVerify) + }; + }); +}; + /** * Verify User. Checks for existence of self signatures, revocation signatures * and validity of self signature diff --git a/test/general/key.js b/test/general/key.js index 0e211c5f..d154e1c7 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -303,7 +303,7 @@ describe('Key', function() { '=MVfN', '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); -var pgp_desktop_pub = + var pgp_desktop_pub = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', 'Version: Encryption Desktop 10.3.0 (Build 9307)', '', @@ -342,7 +342,7 @@ var pgp_desktop_pub = '=dVeR', '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); -var pgp_desktop_priv = + var pgp_desktop_priv = ['-----BEGIN PGP PRIVATE KEY BLOCK-----', 'Version: Encryption Desktop 10.3.0 (Build 9307)', '', @@ -536,6 +536,57 @@ var pgp_desktop_priv = done(); }); + var multi_uid_key = + ['-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v1', + '', + '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', + 'ljIYQms3pCv1ja6bLlNKpPII', + '=qxBI', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + + var wrong_key = + ['-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: OpenPGP.js v0.9.0', + '', + 'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5', + 'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg', + 'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM', + 'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq', + 'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1', + '=6XMW', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + it('Parsing armored text with two keys', function(done) { var pubKeys = openpgp.key.readArmored(twoKeys); expect(pubKeys).to.exist; @@ -890,6 +941,80 @@ var pgp_desktop_priv = done(); }).catch(done); }); + + it('Sign and verify key - primary user', function(done) { + var key = openpgp.key.readArmored(pub_sig_test).keys[0]; + var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; + privateKey.decrypt('hello world'); + key = key.signPrimaryUser([privateKey]); + var signatures = key.verifyPrimaryUser([privateKey]); + expect(signatures.length).to.equal(2); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; + done(); + }); + + it('Sign key and verify with wrong key - primary user', function(done) { + var key = openpgp.key.readArmored(pub_sig_test).keys[0]; + var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; + var wrongKey = openpgp.key.readArmored(wrong_key).keys[0]; + privateKey.decrypt('hello world'); + key = key.signPrimaryUser([privateKey]); + var signatures = key.verifyPrimaryUser([wrongKey]); + expect(signatures.length).to.equal(2); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.null; + done(); + }); + + it('Sign and verify key - all users', function(done) { + var key = openpgp.key.readArmored(multi_uid_key).keys[0]; + var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; + privateKey.decrypt('hello world'); + key = key.signAllUsers([privateKey]); + var signatures = key.verifyAllUsers([privateKey]); + expect(signatures.length).to.equal(4); + expect(signatures[0].userid).to.equal(key.users[0].userId.userid); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].userid).to.equal(key.users[0].userId.userid); + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; + expect(signatures[2].userid).to.equal(key.users[1].userId.userid); + expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[2].valid).to.be.null; + expect(signatures[3].userid).to.equal(key.users[1].userId.userid); + expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[3].valid).to.be.true; + done(); + }); + + it('Sign key and verify with wrong key - all users', function(done) { + var key = openpgp.key.readArmored(multi_uid_key).keys[0]; + var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; + var wrongKey = openpgp.key.readArmored(wrong_key).keys[0]; + privateKey.decrypt('hello world'); + key = key.signAllUsers([privateKey]); + var signatures = key.verifyAllUsers([wrongKey]); + expect(signatures.length).to.equal(4); + expect(signatures[0].userid).to.equal(key.users[0].userId.userid); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].userid).to.equal(key.users[0].userId.userid); + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.null; + expect(signatures[2].userid).to.equal(key.users[1].userId.userid); + expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[2].valid).to.be.null; + expect(signatures[3].userid).to.equal(key.users[1].userId.userid); + expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[3].valid).to.be.null; + done(); + }); it('Reformat key without passphrase', function(done) { var userId1 = 'test1 '; var userId2 = 'test2 '; diff --git a/test/general/signature.js b/test/general/signature.js index 81eb6343..15c72784 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -654,8 +654,8 @@ describe("Signature", function() { done(); }); }); - - it('Verify signed public key', function(done) { + + it('Verify signed key', function(done) { var signedArmor = [ '-----BEGIN PGP PUBLIC KEY BLOCK-----', 'Version: GnuPG v1', @@ -682,14 +682,15 @@ describe("Signature", function() { '=fvK7', '-----END PGP PUBLIC KEY BLOCK-----' ].join('\n'); - - var sig_key = openpgp.key.readArmored(signedArmor).keys[0]; - var pub_key = openpgp.key.readArmored(priv_key_arm1).keys[0].toPublic(); - openpgp.verifyPublicKey({ publicKey: sig_key, publicKeys: [pub_key] }).then(function(verified) { - expect(verified.signatures[0].valid).to.be.true; - expect(verified.signatures[0].keyid.toHex()).to.equal(pub_key.primaryKey.getKeyId().toHex()); - done(); - }); + + var signedKey = openpgp.key.readArmored(signedArmor).keys[0]; + var signerKey = openpgp.key.readArmored(priv_key_arm1).keys[0]; + var signatures = signedKey.verifyPrimaryUser([signerKey]); + expect(signatures[0].valid).to.be.null; + expect(signatures[0].keyid.toHex()).to.equal(signedKey.primaryKey.getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; + expect(signatures[1].keyid.toHex()).to.equal(signerKey.primaryKey.getKeyId().toHex()); + done(); }); }); From d9cb8e681d479d228c51e57d5ed32857c3466d83 Mon Sep 17 00:00:00 2001 From: Aydar Zartdinov Date: Fri, 10 Feb 2017 12:12:52 +0300 Subject: [PATCH 4/6] Array.prototype.find replaced for Node.js 0.12 and IE 11 --- src/key.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/key.js b/src/key.js index b91dbc2e..49ee75b3 100644 --- a/src/key.js +++ b/src/key.js @@ -834,8 +834,9 @@ User.prototype.sign = function(primaryKey, privateKeys) { */ User.prototype.verifyAllSignatures = function(primaryKey, keys) { var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; - return this.selfCertifications.concat(this.otherCertifications).map(signaturePacket => { - var keyPacket = keys.find(key => key.getSigningKeyPacket(signaturePacket.issuerKeyId)) || null; + var certificates = this.selfCertifications.concat(this.otherCertifications || []); + return certificates.map(signaturePacket => { + var keyPacket = keys.filter(key => key.getSigningKeyPacket(signaturePacket.issuerKeyId))[0] || null; return { keyid: signaturePacket.issuerKeyId, valid: keyPacket && signaturePacket.verify(keyPacket.primaryKey, dataToVerify) From d5e88c7c79fc03a6cb0f95043ae4e68db159671b Mon Sep 17 00:00:00 2001 From: Aydar Zartdinov Date: Sat, 4 Mar 2017 18:47:33 +0000 Subject: [PATCH 5/6] Prevent self signing by fingerprint instead of keyId --- src/key.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/key.js b/src/key.js index 49ee75b3..d2749d85 100644 --- a/src/key.js +++ b/src/key.js @@ -802,8 +802,8 @@ User.prototype.sign = function(primaryKey, privateKeys) { if (privateKey.isPublic()) { throw new Error('Need private key for signing'); } - if (privateKey.primaryKey.getKeyId().equals(primaryKey.getKeyId())) { - throw new Error('Not implemented for self signing'); + if (privateKey.primaryKey.getFingerprint() === primaryKey.getFingerprint()) { + throw new Error('Not implemented for self signing'); } signingKeyPacket = privateKey.getSigningKeyPacket(); if (!signingKeyPacket) { From be776c61b19349153d07474c5ac77b3a18875fbb Mon Sep 17 00:00:00 2001 From: Aydar Zartdinov Date: Sat, 4 Mar 2017 19:27:27 +0000 Subject: [PATCH 6/6] Validate signatures by all suitable keys --- src/key.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/key.js b/src/key.js index d2749d85..6c6125c1 100644 --- a/src/key.js +++ b/src/key.js @@ -836,11 +836,12 @@ User.prototype.verifyAllSignatures = function(primaryKey, keys) { var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; var certificates = this.selfCertifications.concat(this.otherCertifications || []); return certificates.map(signaturePacket => { - var keyPacket = keys.filter(key => key.getSigningKeyPacket(signaturePacket.issuerKeyId))[0] || null; - return { - keyid: signaturePacket.issuerKeyId, - valid: keyPacket && signaturePacket.verify(keyPacket.primaryKey, dataToVerify) - }; + var keyPackets = keys.filter(key => key.getSigningKeyPacket(signaturePacket.issuerKeyId)); + var valid = null; + if (keyPackets.length > 0) { + valid = keyPackets.some(keyPacket => signaturePacket.verify(keyPacket.primaryKey, dataToVerify)); + } + return { keyid: signaturePacket.issuerKeyId, valid: valid }; }); };