Added ability to sign and verify public keys

This commit is contained in:
Aydar Zartdinov 2017-02-10 12:12:05 +03:00
parent 3d32898250
commit 5140a946e5
4 changed files with 241 additions and 16 deletions

View File

@ -507,11 +507,11 @@ function getExpirationTime(keyPacket, selfCertificate) {
Key.prototype.getPrimaryUser = function() { Key.prototype.getPrimaryUser = function() {
var primUser = []; var primUser = [];
for (var i = 0; i < this.users.length; i++) { 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; continue;
} }
for (var j = 0; j < this.users[i].selfCertifications.length; j++) { 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 // sort by primary user flag and signature creation time
@ -638,6 +638,86 @@ Key.prototype.revoke = function() {
}; };
/**
* Signs the public key
* @param {Array<module:key~Key>} 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<module:key~Key>} 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 * @class
* @classdesc Class that represents an user ID or attribute packet and the relevant signatures. * @classdesc Class that represents an user ID or attribute packet and the relevant signatures.

View File

@ -34,7 +34,7 @@
import * as messageLib from './message.js'; import * as messageLib from './message.js';
import * as cleartext from './cleartext.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 config from './config/config.js';
import util from './util'; import util from './util';
import AsyncProxy from './worker/async_proxy.js'; 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 asyncProxy.delegate('generateKey', options);
} }
return key.generate(options).then(newKey => ({ return keyLib.generate(options).then(newKey => ({
key: newKey, key: newKey,
privateKeyArmored: newKey.armor(), 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<Key>} privateKeys array of keys or single key with decrypted secret key data to sign public key
* @return {Promise<String|Key>} 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<Key>} publicKeys array of publicKeys or single key, to verify signatures
* @return {Promise<Object>} 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 // // Helper functions //
@ -396,6 +455,11 @@ function checkCleartextMessage(message) {
throw new Error('Parameter [message] needs to be of type CleartextMessage'); 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. * Format user ids for internal use.

View File

@ -158,6 +158,20 @@ var priv_key_de =
'=kyeP', '=kyeP',
'-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); '-----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 passphrase = 'hello world';
var plaintext = 'short message\nnext line\n한국어/조선말'; var plaintext = 'short message\nnext line\n한국어/조선말';
var password1 = 'I am a password'; 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() { 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() { beforeEach(function() {
expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; 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();
});
});
});
} }
}); });

View File

@ -654,5 +654,42 @@ describe("Signature", function() {
done(); 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();
});
});
}); });