Merge pull request #417 from zartdinov/master
Added ability to sign and verify public keys
This commit is contained in:
commit
ebc37f3a7d
120
src/key.js
120
src/key.js
|
@ -511,7 +511,7 @@ Key.prototype.getPrimaryUser = function() {
|
|||
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,64 @@ Key.prototype.revoke = function() {
|
|||
|
||||
};
|
||||
|
||||
/**
|
||||
* Signs primary user of 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.signPrimaryUser = function(privateKeys) {
|
||||
var {index, user} = this.getPrimaryUser() || {};
|
||||
if (!user) {
|
||||
throw new Error('Could not find primary user');
|
||||
}
|
||||
user = user.sign(this.primaryKey, privateKeys);
|
||||
var key = new Key(this.toPacketlist());
|
||||
key.users[index] = user;
|
||||
return key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Signs all users of key
|
||||
* @param {Array<module:key~Key>} 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<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.verifyPrimaryUser = function(keys) {
|
||||
var {user} = this.getPrimaryUser() || {};
|
||||
if (!user) {
|
||||
throw new Error('Could not find primary user');
|
||||
}
|
||||
return user.verifyAllSignatures(this.primaryKey, keys);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies all users of key
|
||||
* @param {Array<module:key~Key>} 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
|
||||
}))
|
||||
);
|
||||
}, []);
|
||||
};
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @classdesc Class that represents an user ID or attribute packet and the relevant signatures.
|
||||
|
@ -727,6 +785,66 @@ 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<module:key~Key>} 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.getFingerprint() === primaryKey.getFingerprint()) {
|
||||
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<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
|
||||
*/
|
||||
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 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 };
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify User. Checks for existence of self signatures, revocation signatures
|
||||
* and validity of self signature
|
||||
|
|
|
@ -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 <a@b.com>';
|
||||
var userId2 = 'test2 <b@a.com>';
|
||||
|
|
|
@ -655,4 +655,42 @@ describe("Signature", function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('Verify signed 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 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();
|
||||
});
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user