From 1ed7943bf93cdd5801c84bceb9d26291c2eea295 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Tue, 27 Feb 2018 22:55:06 +0100 Subject: [PATCH] Create openpgp.revokeKey --- README.md | 24 +++++++ src/index.js | 2 +- src/key.js | 65 +++++++++++++++++ src/openpgp.js | 97 +++++++++++++++++++++----- test/general/key.js | 150 ++++++++++++++++++++++++++++++++++++++++ test/general/openpgp.js | 36 ++++++++++ 6 files changed, 357 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a62a6641..92bbfb61 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,30 @@ var options = { openpgp.generateKey(options).then(function(key) { var privkey = key.privateKeyArmored; // '-----BEGIN PGP PRIVATE KEY BLOCK ... ' var pubkey = key.publicKeyArmored; // '-----BEGIN PGP PUBLIC KEY BLOCK ... ' + var revocationSignature = key.revocationSignature; // '-----BEGIN PGP PUBLIC KEY BLOCK ... ' +}); +``` + +#### Revoke a key + +Using a revocation signature: +```js +var options = { + key: openpgp.key.readArmored(pubkey).keys[0], + revocationSignature: revocationSignature +}; +``` + +Using the private key: +```js +var options = { + key: openpgp.key.readArmored(privkey).keys[0] +}; +``` + +```js +openpgp.revokeKey(options).then(function(key) { + var pubkey = key.publicKeyArmored; // '-----BEGIN PGP PUBLIC KEY BLOCK ... ' }); ``` diff --git a/src/index.js b/src/index.js index 7b2b7f14..e0cd2f3c 100644 --- a/src/index.js +++ b/src/index.js @@ -19,7 +19,7 @@ export default openpgp; */ export { encrypt, decrypt, sign, verify, - generateKey, reformatKey, decryptKey, + generateKey, reformatKey, revokeKey, decryptKey, encryptSessionKey, decryptSessionKeys, initWorker, getWorker, destroyWorker } from './openpgp'; diff --git a/src/key.js b/src/key.js index 118ed8ce..8713cfac 100644 --- a/src/key.js +++ b/src/key.js @@ -665,6 +665,56 @@ Key.prototype.revoke = async function(privateKey, { return key; }; +/** + * Get revocation certificate from a revoked key. + * (To get a revocation certificate for an unrevoked key, call revoke() first.) + * @return {String} armored revocation certificate + */ +Key.prototype.getRevocationCertificate = function() { + if (this.revocationSignature) { + const commentstring = config.commentstring; + config.commentstring = 'This is a revocation certificate'; + try { + const packetlist = new packet.List(); + packetlist.push(this.revocationSignature); + return armor.encode(enums.armor.public_key, packetlist.write()); + } finally { + // Restore comment string. armor.encode() shouldn't throw, but just to be sure it's wrapped in a try/finally + config.commentstring = commentstring; + } + } +}; + +/** + * Applies a revocation certificate to a key + * @param {String} revocationCertificate armored revocation certificate + * @return {module:key~Key} new revoked key + */ +Key.prototype.applyRevocationCertificate = async function(revocationCertificate) { + const input = armor.decode(revocationCertificate); + if (input.type !== enums.armor.public_key) { + throw new Error('Armored text not of type public key'); + } + const packetlist = new packet.List(); + packetlist.read(input.data); + const revocationSignature = packetlist.findPacket(enums.packet.signature); + if (!revocationSignature || revocationSignature.signatureType !== enums.signature.key_revocation) { + throw new Error('Could not find revocation signature packet'); + } + if (!revocationSignature.issuerKeyId.equals(this.primaryKey.getKeyId())) { + throw new Error('Revocation signature does not match key'); + } + if (revocationSignature.isExpired()) { + throw new Error('Revocation signature is expired'); + } + if (!await revocationSignature.verify(this.primaryKey, { key: this.primaryKey })) { + throw new Error('Could not verify revocation signature'); + } + const key = new Key(this.toPacketlist()); + key.revocationSignature = revocationSignature; + return key; +}; + /** * Signs primary user of key * @param {Array} privateKey decrypted private keys for signing @@ -1185,6 +1235,7 @@ export function readArmored(armoredText) { * @param {Date} date Override the creation date of the key and the key signatures * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt + * @param {Boolean} [options.revoked=false] Whether the key should include a revocation signature * @returns {Promise} * @async * @static @@ -1266,6 +1317,7 @@ export async function generate(options) { * @param {Date} date Override the creation date of the key and the key signatures * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] * + * @param {Boolean} [options.revoked=false] Whether the key should include a revocation signature * @returns {Promise} * @async * @static @@ -1416,6 +1468,19 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { }); }); + if (options.revoked) { + const dataToSign = {}; + dataToSign.key = secretKeyPacket; + const revocationSignaturePacket = new packet.Signature(); + revocationSignaturePacket.signatureType = enums.signature.key_revocation; + revocationSignaturePacket.reasonForRevocationFlag = enums.reasonForRevocation.no_reason; + revocationSignaturePacket.reasonForRevocationString = ''; + revocationSignaturePacket.publicKeyAlgorithm = options.keyType; + revocationSignaturePacket.hashAlgorithm = getPreferredHashAlgo(secretKeyPacket); + await revocationSignaturePacket.sign(secretKeyPacket, dataToSign); + packetlist.push(revocationSignaturePacket); + } + // set passphrase protection if (options.passphrase) { secretKeyPacket.clearPrivateParams(); diff --git a/src/openpgp.js b/src/openpgp.js index 7bc4706e..37c279a1 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -108,15 +108,16 @@ export function destroyWorker() { * @param {Date} date (optional) override the creation date of the key and the key signatures * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt + * @param {Boolean} revocationCertificate (optional) Whether the returned object should include a revocation certificate to revoke the public key * @returns {Promise} The generated key object in the form: - * { key:Key, privateKeyArmored:String, publicKeyArmored:String } + * { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String } * @async * @static */ -export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}] }) { +export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}], revocationCertificate=true }) { userIds = toArray(userIds); - const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys }; + const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys, revocationCertificate }; if (util.getWebCryptoAll() && numBits < 2048) { throw new Error('numBits should be 2048 or 4096, found: ' + numBits); } @@ -125,13 +126,21 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira return asyncProxy.delegate('generateKey', options); } - return generate(options).then(key => ({ + options.revoked = options.revocationCertificate; - key: key, - privateKeyArmored: key.armor(), - publicKeyArmored: key.toPublic().armor() + return generate(options).then(key => { + const revocationCertificate = key.getRevocationCertificate(); + key.revocationSignature = null; - })).catch(onError.bind(null, 'Error generating keypair')); + return { + + key: key, + privateKeyArmored: key.armor(), + publicKeyArmored: key.toPublic().armor(), + revocationCertificate: revocationCertificate + + }; + }).catch(onError.bind(null, 'Error generating keypair')); } /** @@ -140,25 +149,81 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira * @param {Array} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key * @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires + * @param {Boolean} revocationCertificate (optional) Whether the returned object should include a revocation certificate to revoke the public key * @returns {Promise} The generated key object in the form: - * { key:Key, privateKeyArmored:String, publicKeyArmored:String } + * { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String } * @async * @static */ -export function reformatKey({privateKey, userIds=[], passphrase="", keyExpirationTime=0, date}) { +export function reformatKey({privateKey, userIds=[], passphrase="", keyExpirationTime=0, date, revocationCertificate=true}) { userIds = toArray(userIds); - const options = { privateKey, userIds, passphrase, keyExpirationTime, date}; + const options = { privateKey, userIds, passphrase, keyExpirationTime, date, revocationCertificate=true}; if (asyncProxy) { return asyncProxy.delegate('reformatKey', options); } - return reformat(options).then(key => ({ + options.revoked = options.revocationCertificate; - key: key, - privateKeyArmored: key.armor(), - publicKeyArmored: key.toPublic().armor() + return reformat(options).then(key => { + const revocationCertificate = key.getRevocationCertificate(); + key.revocationSignature = null; - })).catch(onError.bind(null, 'Error reformatting keypair')); + return { + + key: key, + privateKeyArmored: key.armor(), + publicKeyArmored: key.toPublic().armor(), + revocationCertificate: revocationCertificate + + }; + }).catch(onError.bind(null, 'Error reformatting keypair')); +} + +/** + * Revokes a key. Requires either a private key or a revocation certificate. + * If a revocation certificate is passed, the reasonForRevocation parameters will be ignored. + * @param {Key} key (optional) public or private key to revoke + * @param {String} revocationCertificate (optional) revocation certificate to revoke the key with + * @param {Object} reasonForRevocation (optional) object indicating the reason for revocation + * @param {module:enums.reasonForRevocation} reasonForRevocation.flag (optional) flag indicating the reason for revocation + * @param {String} reasonForRevocation.string (optional) string explaining the reason for revocation + * @return {Promise} The revoked key object in the form: + * { privateKey:Key, privateKeyArmored:String, publicKey:Key, publicKeyArmored:String } + * (if private key is passed) or { publicKey:Key, publicKeyArmored:String } (otherwise) + * @static + */ +export function revokeKey({ + key, revocationCertificate, reasonForRevocation +} = {}) { + const options = { + key, revocationCertificate, reasonForRevocation + }; + + if (!util.getWebCryptoAll() && asyncProxy) { // use web worker if web crypto apis are not supported + return asyncProxy.delegate('revokeKey', options); + } + + return Promise.resolve().then(() => { + if (revocationCertificate) { + return key.applyRevocationCertificate(revocationCertificate); + } else { + return key.revoke(key, reasonForRevocation); + } + }).then(key => { + if(key.isPrivate()) { + const publicKey = key.toPublic(); + return { + privateKey: key, + privateKeyArmored: key.armor(), + publicKey: publicKey, + publicKeyArmored: publicKey.armor() + }; + } + return { + publicKey: key, + publicKeyArmored: key.armor() + }; + }).catch(onError.bind(null, 'Error revoking key')); } /** diff --git a/test/general/key.js b/test/general/key.js index 4ee5970e..4d6f319f 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -114,6 +114,97 @@ function tests() { 'hz3tYjKhoFTKEIq3y3Pp', '=h/aX', '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + + const pub_key_arm4 = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG 2.1.15 (GNU/Linux) + +mI0EWpoNLAEEAMoO8dfnLvvCze1hjWcr8t1fMdndFQc1fAM7dm6sbqrdlaAz+Dab +zF3F9UhIOCcABRm+QHyZlgEsoQpHF/7sWflUK1FpoxdORINtIDilukUkMZ0NnIaD ++8pRutdSczPNFvSImSzZNCyLzvDCGMO3+Xeaa6pViSPEeBwhXWJUuHYtABEBAAG0 +IkpvZSBVc2VyIDxqb2UudXNlckBwcm90b25tYWlsLmNvbT6ItwQTAQgAIQUCWpoN +LAIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRBYtrN4WUnCtoUjBACi6qVb +noQJ1aOaOyoBZscDIO5XZHZK4L9V9uJJhD9v1qRF5s0PRiG969EwFlvjqIlLiRej +KSxvE/1rcym4GwBUndku1fMM+weTWHNtRn9+BPzN/4eKZbUbY3fHtlk+Lde3N+CZ +vrGKS/ICtbtuAfZL0LdzzqnNuBUXlO6EpG5C3riNBFqaDSwBBADDURzGkpTn/FTT +xHyheai+zTOUmy7N1ViCRPkErIeD606tZf/sKqAnEChfECeZJReYydN1B3O8QOyI +Ly/rH0DS2bt/6juhknPVGHPUAyNxHmiHYXTUgGPEX1QfusjzBcfIk6vHjYBiRm/I +u9iwrzCwypA4dWDZSTZuFrVsf4n+twARAQABiJ8EGAEIAAkFAlqaDSwCGwwACgkQ +WLazeFlJwrZQEAQAuUrp9Qp76CnKqUsUjcVxq7DJBi/lewyGGYSVAFt6/0Xyg/8Y +TEa/c4Dri/HMOtrfbgjp/doIVaZLOXZYfqRcpy3z0M6BierOPB3D+fdaTfd7gIrQ +nGHIp2NmbJZnYgl8Ps23qF+LKTa1eE+AmMQYzUHSGuka2lp6OglwWzg/dEw= +=/vbH +-----END PGP PUBLIC KEY BLOCK-----`; + + const priv_key_arm4 = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG 2.1.15 (GNU/Linux) + +lQIGBFqaDSwBBADKDvHX5y77ws3tYY1nK/LdXzHZ3RUHNXwDO3ZurG6q3ZWgM/g2 +m8xdxfVISDgnAAUZvkB8mZYBLKEKRxf+7Fn5VCtRaaMXTkSDbSA4pbpFJDGdDZyG +g/vKUbrXUnMzzRb0iJks2TQsi87wwhjDt/l3mmuqVYkjxHgcIV1iVLh2LQARAQAB +/gcDAoZ8RULY7umS4fVGPmTuETCnOOTGancXT5r7chKyfFXlyVU4ULvTdLwdFtqx +Vl9tNyED31nIiRP1CTmZLeaVScNGfVLjo8nvpMZUVopw5UdaFADeVTpwVdtp7ru+ +IgH4ynrRMgMGh7/dgBzIP8WN4w8uBPK5G4bS34NNiREkVoZ3oh4dA/6aeYfW7lVV +cYRl2F7++AGfqS+FpLsE8KjFU2z8POJjWMN1nYKwjNa+beEO0BFYdUFvMzU7eUHA +/G0xWAhYvNyuJHE4imgYmCy1OZeawc9h8YGeaQJCh2NTVzaD9HRu0xmz93bNF19q +bfUZJC7mC6WzKsRXHX0JmzH+9DShUqGnkRl5fMo2UhQMpSxsMT4dU/Ji4q+t96oy +K6g3DMJr5OtZML3XKxGmdy0CgepkG1aikrC9qLfBgxjqi+uTewcbrS9lAOfrZg4N +jwt1FLEK8gu7aOeczdW/pFOHOCrX1DnpF81JKJ1a7hz5JRP1m+ffqwm0IkpvZSBV +c2VyIDxqb2UudXNlckBwcm90b25tYWlsLmNvbT6ItwQTAQgAIQUCWpoNLAIbAwUL +CQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRBYtrN4WUnCtoUjBACi6qVbnoQJ1aOa +OyoBZscDIO5XZHZK4L9V9uJJhD9v1qRF5s0PRiG969EwFlvjqIlLiRejKSxvE/1r +cym4GwBUndku1fMM+weTWHNtRn9+BPzN/4eKZbUbY3fHtlk+Lde3N+CZvrGKS/IC +tbtuAfZL0LdzzqnNuBUXlO6EpG5C3p0CBgRamg0sAQQAw1EcxpKU5/xU08R8oXmo +vs0zlJsuzdVYgkT5BKyHg+tOrWX/7CqgJxAoXxAnmSUXmMnTdQdzvEDsiC8v6x9A +0tm7f+o7oZJz1Rhz1AMjcR5oh2F01IBjxF9UH7rI8wXHyJOrx42AYkZvyLvYsK8w +sMqQOHVg2Uk2bha1bH+J/rcAEQEAAf4HAwLwNvRIoBFS3OHTIYirkr4sHzSkWFJx +xDPozovXgCq7BoCXDMaSIQLwZqEfb+SabYtk7nLSnG2Y2mgwb9swZuBZEWuQjZk7 +lX1MvuZ0Ih2QdQSMEJk8sEsMoBGHHdHh/MZO4a27+5B9OceDfnEZZcGSOweUuu1n +IlgWcgrM40q4S3Mt39FXFgdJWnpd93hAokKDHklUGMdMLw/02dGVRkJmvUp9qdhe +c2njq9HSeYwqbY2rYgcNsF2ZcCLt9UXA2dOG4X2c2mPfjKuTRZUPxNKh6JfL3mlu +rBdd/z8gQHoKObyaarVwN3HAbtP0+6Z8a9/wDYj1K9ZCoHuEtKq1qq5J2Ec8+Yzl +K0Zlcs760LiYUr69CninMrnbDNnAhrYAcyJS42viUADPv9g+CBbyanB4KyE4UNrZ +BCB296lOEW4v1IZVNrNvqrbka3/p0qqBJiFTh7eT3zXpRNArFZDmLCUEEm53qT1a +PO/MyYUGTTMRAzTmNTiPiJ8EGAEIAAkFAlqaDSwCGwwACgkQWLazeFlJwrZQEAQA +uUrp9Qp76CnKqUsUjcVxq7DJBi/lewyGGYSVAFt6/0Xyg/8YTEa/c4Dri/HMOtrf +bgjp/doIVaZLOXZYfqRcpy3z0M6BierOPB3D+fdaTfd7gIrQnGHIp2NmbJZnYgl8 +Ps23qF+LKTa1eE+AmMQYzUHSGuka2lp6OglwWzg/dEw= +=mr3M +-----END PGP PRIVATE KEY BLOCK-----`; + + const revocation_certificate_arm4 = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG 2.1.15 (GNU/Linux) +Comment: This is a revocation certificate + +iJ8EIAEIAAkFAlqaDT0CHQAACgkQWLazeFlJwrbOaAP/V38FhBrUy4XYgt8ZX22G +ov6IFDNoyRKafSuz7Rg+8K8cf+0MAsSi52ueKfsbPxQ+I1vPeaEuEYbwTjtbvM+M +vZcX+VNYdsc1iZeNaT4ayA+2LrCN/xgFj0nrExHqcZAjgBZ9pvKghAqdK4Zb2Ghb +7chPiLLNWJCMtL4bo7a1X84= +=HcWg +-----END PGP PUBLIC KEY BLOCK-----`; + + const revoked_key_arm4 = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG 2.1.15 (GNU/Linux) + +mI0EWpoNLAEEAMoO8dfnLvvCze1hjWcr8t1fMdndFQc1fAM7dm6sbqrdlaAz+Dab +zF3F9UhIOCcABRm+QHyZlgEsoQpHF/7sWflUK1FpoxdORINtIDilukUkMZ0NnIaD ++8pRutdSczPNFvSImSzZNCyLzvDCGMO3+Xeaa6pViSPEeBwhXWJUuHYtABEBAAGI +nwQgAQgACQUCWpoNPQIdAAAKCRBYtrN4WUnCts5oA/9XfwWEGtTLhdiC3xlfbYai +/ogUM2jJEpp9K7PtGD7wrxx/7QwCxKLna54p+xs/FD4jW895oS4RhvBOO1u8z4y9 +lxf5U1h2xzWJl41pPhrID7YusI3/GAWPSesTEepxkCOAFn2m8qCECp0rhlvYaFvt +yE+Iss1YkIy0vhujtrVfzrQiSm9lIFVzZXIgPGpvZS51c2VyQHByb3Rvbm1haWwu +Y29tPoi3BBMBCAAhBQJamg0sAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJ +EFi2s3hZScK2hSMEAKLqpVuehAnVo5o7KgFmxwMg7ldkdkrgv1X24kmEP2/WpEXm +zQ9GIb3r0TAWW+OoiUuJF6MpLG8T/WtzKbgbAFSd2S7V8wz7B5NYc21Gf34E/M3/ +h4pltRtjd8e2WT4t17c34Jm+sYpL8gK1u24B9kvQt3POqc24FReU7oSkbkLeuI0E +WpoNLAEEAMNRHMaSlOf8VNPEfKF5qL7NM5SbLs3VWIJE+QSsh4PrTq1l/+wqoCcQ +KF8QJ5klF5jJ03UHc7xA7IgvL+sfQNLZu3/qO6GSc9UYc9QDI3EeaIdhdNSAY8Rf +VB+6yPMFx8iTq8eNgGJGb8i72LCvMLDKkDh1YNlJNm4WtWx/if63ABEBAAGInwQY +AQgACQUCWpoNLAIbDAAKCRBYtrN4WUnCtlAQBAC5Sun1CnvoKcqpSxSNxXGrsMkG +L+V7DIYZhJUAW3r/RfKD/xhMRr9zgOuL8cw62t9uCOn92ghVpks5dlh+pFynLfPQ +zoGJ6s48HcP591pN93uAitCcYcinY2ZslmdiCXw+zbeoX4spNrV4T4CYxBjNQdIa +6RraWno6CXBbOD90TA== +=8d2d +-----END PGP PUBLIC KEY BLOCK-----`; const twoKeys = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', @@ -1467,6 +1558,34 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + }); }); + it('applyRevocationCertificate() should produce the same revoked key as GnuPG', function() { + const pubKey = openpgp.key.readArmored(pub_key_arm4).keys[0]; + + return pubKey.applyRevocationCertificate(revocation_certificate_arm4).then(revKey => { + expect(revKey.armor()).to.equal(openpgp.key.readArmored(revoked_key_arm4).keys[0].armor()); + }); + }); + + it('getRevocationCertificate() should produce the same revocation certificate as GnuPG', function() { + const revKey = openpgp.key.readArmored(revoked_key_arm4).keys[0]; + const revocationCertificate = revKey.getRevocationCertificate(); + + const input = openpgp.armor.decode(revocation_certificate_arm4); + const packetlist = new openpgp.packet.List(); + packetlist.read(input.data); + const armored = openpgp.armor.encode(openpgp.enums.armor.public_key, packetlist.write()); + + expect(revocationCertificate.replace(/^Comment: .*$/m, '')).to.equal(armored.replace(/^Comment: .*$/m, '')); + }); + + it('getRevocationCertificate() should have an appropriate comment', function() { + const revKey = openpgp.key.readArmored(revoked_key_arm4).keys[0]; + const revocationCertificate = revKey.getRevocationCertificate(); + + expect(revocationCertificate).to.match(/Comment: This is a revocation certificate/); + expect(revKey.armor()).not.to.match(/Comment: This is a revocation certificate/); + }); + it("getPreferredAlgo('symmetric') - one key - AES256", async function() { const key1 = openpgp.key.readArmored(twoKeys).keys[0]; const prefAlgo = await openpgp.key.getPreferredAlgo('symmetric', [key1]); @@ -2012,6 +2131,37 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + }); }); + it('Revoke generated key with revocation certificate', function() { + const opt = {numBits: 512, userIds: 'test1 ', passphrase: '1234'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + return openpgp.generateKey(opt).then(function(original) { + return openpgp.revokeKey({key: original.key.toPublic(), revocationCertificate: original.revocationCertificate}).then(function(revKey) { + revKey = revKey.publicKey; + expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason); + expect(revKey.revocationSignature.reasonForRevocationString).to.equal(''); + return revKey.verifyPrimaryKey().then(function(status) { + expect(status).to.equal(openpgp.enums.keyStatus.revoked); + }); + }); + }); + }); + + it('Revoke generated key with private key', function() { + const opt = {numBits: 512, userIds: 'test1 ', passphrase: '1234'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + return openpgp.generateKey(opt).then(function(original) { + original.key.decrypt('1234'); + return openpgp.revokeKey({key: original.key, reasonForRevocation: {string: 'Testing key revocation'}}).then(function(revKey) { + revKey = revKey.publicKey; + expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason); + expect(revKey.revocationSignature.reasonForRevocationString).to.equal('Testing key revocation'); + return revKey.verifyPrimaryKey().then(function(status) { + expect(status).to.equal(openpgp.enums.keyStatus.revoked); + }); + }); + }); + }); + it('Merge key with another key with non-ID user attributes', function(done) { const key = openpgp.key.readArmored(mergeKey1).keys[0]; const updateKey = openpgp.key.readArmored(mergeKey2).keys[0]; diff --git a/test/general/openpgp.js b/test/general/openpgp.js index a94c676c..4b9f9aac 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -484,6 +484,10 @@ describe('OpenPGP.js public api tests', function() { return 'pub_key'; } }; + }, + getRevocationCertificate: function() {}, + removeRevocationCertificate: function() { + return this; } }; keyGenStub = stub(openpgp.key, 'generate'); @@ -514,6 +518,8 @@ describe('OpenPGP.js public api tests', function() { curve: "", date: now, subkeys: [], + revocationCertificate: true, + revoked: true, }).calledOnce).to.be.true; expect(newKey.key).to.exist; expect(newKey.privateKeyArmored).to.exist; @@ -1855,6 +1861,36 @@ describe('OpenPGP.js public api tests', function() { expect(signatures[0].signature.packets.length).to.equal(1); }); }); + + it.skip('should fail to encrypt with revoked key', function() { + return openpgp.revokeKey({ + key: privateKey.keys[0] + }).then(function(revKey) { + return openpgp.encrypt({ + data: plaintext, + publicKeys: revKey.publicKey + }).then(function(encrypted) { + throw new Error('Should not encrypt with revoked key'); + }).catch(function(error) { + expect(error.message).to.match(/Could not find valid key packet for encryption/); + }); + }); + }); + + it.skip('should fail to encrypt with revoked subkey', function() { + let clonedKey = privateKey.keys[0].toPublic(); + return clonedKey.subKeys[0].revoke(clonedKey.primaryKey, privateKey.keys[0]).then(function(revSubKey) { + clonedKey.subKeys[0] = revSubKey; + return openpgp.encrypt({ + data: plaintext, + publicKeys: clonedKey + }).then(function(encrypted) { + throw new Error('Should not encrypt with revoked subkey'); + }).catch(function(error) { + expect(error.message).to.match(/Could not find valid key packet for encryption/); + }); + }); + }); }); describe('ELG / DSA encrypt, decrypt, sign, verify', function() {